Compare commits

..

1 Commits

Author SHA1 Message Date
f816ddba5e steinhart_hart: Beta Parameter uom dimensions
The Beta Parameter is not dimensionless, and has unit Kelvin.
Incorporate that into the type system for better checks.
2023-08-30 16:08:14 +08:00
16 changed files with 225 additions and 288 deletions

8
Cargo.lock generated
View File

@ -327,10 +327,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "panic-halt" name = "panic-abort"
version = "0.2.0" version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de96540e0ebde571dc55c73d60ef407c653844e6f9a1e2fdbd40c07b9252d812" checksum = "4e20e6499bbbc412f280b04a42346b356c6fa0753d5fd22b7bd752ff34c778ee"
[[package]] [[package]]
name = "panic-semihosting" name = "panic-semihosting"
@ -563,7 +563,7 @@ dependencies = [
"nb 1.0.0", "nb 1.0.0",
"nom", "nom",
"num-traits", "num-traits",
"panic-halt", "panic-abort",
"panic-semihosting", "panic-semihosting",
"serde", "serde",
"serde-json-core", "serde-json-core",

View File

@ -7,14 +7,14 @@ authors = ["Astro <astro@spaceboyz.net>"]
version = "0.0.0" version = "0.0.0"
keywords = ["thermostat", "laser", "physics"] keywords = ["thermostat", "laser", "physics"]
repository = "https://git.m-labs.hk/M-Labs/thermostat" repository = "https://git.m-labs.hk/M-Labs/thermostat"
edition = "2021" edition = "2018"
[package.metadata.docs.rs] [package.metadata.docs.rs]
features = [] features = []
default-target = "thumbv7em-none-eabihf" default-target = "thumbv7em-none-eabihf"
[dependencies] [dependencies]
panic-halt = "0.2" panic-abort = "0.3"
panic-semihosting = { version = "0.5", optional = true } panic-semihosting = { version = "0.5", optional = true }
log = "0.4" log = "0.4"
bare-metal = "1" bare-metal = "1"

View File

@ -29,7 +29,7 @@ Alternatively, you can install the Rust toolchain without Nix using rustup; see
Connect SWDIO/SWCLK/RST/GND to a programmer such as ST-Link v2.1. Run OpenOCD: Connect SWDIO/SWCLK/RST/GND to a programmer such as ST-Link v2.1. Run OpenOCD:
```shell ```shell
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg
``` ```
You may need to power up the programmer before powering the device. You may need to power up the programmer before powering the device.
@ -64,7 +64,7 @@ On a Windows machine install [st.com](https://st.com) DfuSe USB device firmware
### OpenOCD ### OpenOCD
```shell ```shell
openocd -f interface/stlink.cfg -f target/stm32f4x.cfg -c "program target/thumbv7em-none-eabihf/release/thermostat verify reset;exit" openocd -f interface/stlink-v2-1.cfg -f target/stm32f4x.cfg -c "program target/thumbv7em-none-eabihf/release/thermostat verify reset;exit"
``` ```
## Network ## Network
@ -264,6 +264,7 @@ with the following keys.
| `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` | | `temperature` | Degrees Celsius | Steinhart-Hart conversion result derived from `sens` |
| `pid_engaged` | Boolean | `true` if in closed-loop mode | | `pid_engaged` | Boolean | `true` if in closed-loop mode |
| `i_set` | Amperes | TEC output current | | `i_set` | Amperes | TEC output current |
| `vref` | Volts | MAX1968 VREF (1.5 V) |
| `dac_value` | Volts | AD5680 output derived from `i_set` | | `dac_value` | Volts | AD5680 output derived from `i_set` |
| `dac_feedback` | Volts | ADC measurement of the AD5680 output | | `dac_feedback` | Volts | ADC measurement of the AD5680 output |
| `i_tec` | Volts | MAX1968 TEC current monitor | | `i_tec` | Volts | MAX1968 TEC current monitor |
@ -271,19 +272,18 @@ with the following keys.
| `tec_u_meas` | Volts | Measurement of the voltage across the TEC | | `tec_u_meas` | Volts | Measurement of the voltage across the TEC |
| `pid_output` | Amperes | PID control output | | `pid_output` | Amperes | PID control output |
Note: With Thermostat v2 and below, the voltage and current readouts `i_tec` and `tec_i` are noisy without the hardware fix shown in [this PR][https://git.m-labs.hk/M-Labs/thermostat/pulls/105].
## PID Tuning ## 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). 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
Fan control commands are available for thermostat revisions with an integrated fan system: Fan control is available for the thermostat revisions with integrated fan system. For this purpose four commands are available:
1. `fan` - show fan stats: `fan_pwm`, `abs_max_tec_i`, `auto_mode`, `k_a`, `k_b`, `k_c`. 1. `fan` - show fan stats: `fan_pwm`, `abs_max_tec_i`, `auto_mode`, `k_a`, `k_b`, `k_c`.
2. `fan auto` - enable auto speed controller mode, where fan speed is controlled by the fan curve `fcurve`. 2. `fan auto` - enable auto speed controller mode, which correlates with fan curve `fcurve`.
3. `fan <value>` - set the fan power with the value from `1` to `100` and disable auto mode. There is no way to completely disable the fan. 3. `fan <value>` - set the fan power with the value from `1` to `100` and disable auto mode. There is no way to disable the fan.
Please note that power doesn't correlate with the actual speed linearly. Please note that power doesn't correlate with the actual speed linearly.
4. `fcurve <a> <b> <c>` - set coefficients of the controlling curve `a*x^2 + b*x + c`, where `x` is `abs_max_tec_i/MAX_TEC_I`, a normalized value in range [0,1], 4. `fcurve <a> <b> <c>` - set coefficients of the controlling curve `a*x^2 + b*x + c`, where `x` is `abs_max_tec_i/MAX_TEC_I`,
i.e. the (linear) proportion of current output capacity used, on the channel with the largest current flow. The controlling curve is also clamped to [0,1]. i.e. receives values from 0 to 1 linearly tied to the maximum current. The controlling curve should produce values from 0 to 1,
5. `fcurve default` - restore fan curve coefficients to defaults: `a = 1.0, b = 0.0, c = 0.0`. as below and beyond values would be substituted by 0 and 1 respectively.
5. `fcurve default` - restore fan curve settings to defaults: `a = 1.0, b = 0.0, c = 0.0`.

40
flake.lock generated
View File

@ -1,5 +1,21 @@
{ {
"nodes": { "nodes": {
"mozilla-overlay": {
"flake": false,
"locked": {
"lastModified": 1690536331,
"narHash": "sha256-aRIf2FB2GTdfF7gl13WyETmiV/J7EhBGkSWXfZvlxcA=",
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"rev": "db89c8707edcffefcd8e738459d511543a339ff5",
"type": "github"
},
"original": {
"owner": "mozilla",
"repo": "nixpkgs-mozilla",
"type": "github"
}
},
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1691421349, "lastModified": 1691421349,
@ -18,28 +34,8 @@
}, },
"root": { "root": {
"inputs": { "inputs": {
"nixpkgs": "nixpkgs", "mozilla-overlay": "mozilla-overlay",
"rust-overlay": "rust-overlay" "nixpkgs": "nixpkgs"
}
},
"rust-overlay": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1719281921,
"narHash": "sha256-LIBMfhM9pMOlEvBI757GOK5l0R58SRi6YpwfYMbf4yc=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "b6032d3a404d8a52ecfc8571ff0c26dfbe221d07",
"type": "github"
},
"original": {
"owner": "oxalica",
"repo": "rust-overlay",
"type": "github"
} }
} }
}, },

View File

@ -2,25 +2,32 @@
description = "Firmware for the Sinara 8451 Thermostat"; description = "Firmware for the Sinara 8451 Thermostat";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-23.05; inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-23.05;
inputs.rust-overlay = { inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
url = "github:oxalica/rust-overlay";
inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, rust-overlay }: outputs = { self, nixpkgs, mozilla-overlay }:
let let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import rust-overlay) ]; }; pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
rustManifest = pkgs.fetchurl {
rust = pkgs.rust-bin.stable."1.66.0".default.override { url = "https://static.rust-lang.org/dist/2021-10-26/channel-rust-nightly.toml";
extensions = [ "rust-src" ]; sha256 = "sha256-1hLbypXA+nuH7o3AHCokzSBZAvQxvef4x9+XxO3aBao=";
targets = [ "thumbv7em-none-eabihf" ];
}; };
rustPlatform = pkgs.makeRustPlatform {
targets = [
"thumbv7em-none-eabihf"
];
rustChannelOfTargets = _channel: _date: targets:
(pkgs.lib.rustLib.fromManifestFile rustManifest {
inherit (pkgs) stdenv lib fetchurl patchelf;
}).rust.override {
inherit targets;
extensions = ["rust-src"];
};
rust = rustChannelOfTargets "nightly" null targets;
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
rustc = rust; rustc = rust;
cargo = rust; cargo = rust;
}; });
thermostat = rustPlatform.buildRustPackage rec {
thermostat = rustPlatform.buildRustPackage {
name = "thermostat"; name = "thermostat";
version = "0.0.0"; version = "0.0.0";
@ -51,20 +58,21 @@
in { in {
packages.x86_64-linux = { packages.x86_64-linux = {
inherit thermostat; inherit thermostat;
default = thermostat;
}; };
hydraJobs = { hydraJobs = {
inherit thermostat; inherit thermostat;
}; };
devShells.x86_64-linux.default = pkgs.mkShellNoCC { devShell.x86_64-linux = pkgs.mkShell {
name = "thermostat-dev-shell"; name = "thermostat-dev-shell";
packages = with pkgs; [ buildInputs = with pkgs; [
rust openocd dfu-util rustc cargo
openocd dfu-util
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib numpy matplotlib
]); ]);
}; };
defaultPackage.x86_64-linux = thermostat;
}; };
} }

View File

@ -2,13 +2,11 @@ use smoltcp::time::{Duration, Instant};
use uom::si::{ use uom::si::{
f64::{ f64::{
ElectricPotential, ElectricPotential,
ElectricCurrent,
ElectricalResistance, ElectricalResistance,
ThermodynamicTemperature, ThermodynamicTemperature,
Time, Time,
}, },
electric_potential::volt, electric_potential::volt,
electric_current::ampere,
electrical_resistance::ohm, electrical_resistance::ohm,
thermodynamic_temperature::degree_celsius, thermodynamic_temperature::degree_celsius,
time::millisecond, time::millisecond,
@ -31,7 +29,6 @@ pub struct ChannelState {
/// i_set 0A center point /// i_set 0A center point
pub center: CenterPoint, pub center: CenterPoint,
pub dac_value: ElectricPotential, pub dac_value: ElectricPotential,
pub i_set: ElectricCurrent,
pub pid_engaged: bool, pub pid_engaged: bool,
pub pid: pid::Controller, pub pid: pid::Controller,
pub sh: sh::Parameters, pub sh: sh::Parameters,
@ -47,7 +44,6 @@ impl ChannelState {
adc_interval: Duration::from_millis(100), adc_interval: Duration::from_millis(100),
center: CenterPoint::Vref, center: CenterPoint::Vref,
dac_value: ElectricPotential::new::<volt>(0.0), dac_value: ElectricPotential::new::<volt>(0.0),
i_set: ElectricCurrent::new::<ampere>(0.0),
pid_engaged: false, pid_engaged: false,
pid: pid::Controller::new(pid::Parameters::default()), pid: pid::Controller::new(pid::Parameters::default()),
sh: sh::Parameters::default(), sh: sh::Parameters::default(),

View File

@ -1,6 +1,5 @@
use core::{cmp::max_by, marker::PhantomData}; use core::cmp::max_by;
use heapless::{consts::U2, Vec}; use heapless::{consts::U2, Vec};
use num_traits::Zero;
use serde::{Serialize, Serializer}; use serde::{Serialize, Serializer};
use smoltcp::time::Instant; use smoltcp::time::Instant;
use stm32f4xx_hal::hal; use stm32f4xx_hal::hal;
@ -19,38 +18,15 @@ use crate::{
channel_state::ChannelState, channel_state::ChannelState,
command_parser::{CenterPoint, PwmPin}, command_parser::{CenterPoint, PwmPin},
command_handler::JsonBuffer, command_handler::JsonBuffer,
pins::{self, Channel0VRef, Channel1VRef}, pins,
steinhart_hart, steinhart_hart,
}; };
pub enum PinsAdcReadTarget {
VREF,
DacVfb,
ITec,
VTec,
}
pub const CHANNELS: usize = 2; pub const CHANNELS: usize = 2;
pub const R_SENSE: f64 = 0.05; pub const R_SENSE: f64 = 0.05;
// From design specs
pub const MAX_TEC_I: ElectricCurrent = ElectricCurrent {
dimension: PhantomData,
units: PhantomData,
value: 2.0,
};
pub const MAX_TEC_V: ElectricPotential = ElectricPotential {
dimension: PhantomData,
units: PhantomData,
value: 4.0,
};
// DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range // DAC chip outputs 0-5v, which is then passed through a resistor dividor to provide 0-3v range
const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential { const DAC_OUT_V_MAX: f64 = 3.0;
dimension: PhantomData,
units: PhantomData,
value: 3.0,
};
// TODO: -pub // TODO: -pub
pub struct Channels { pub struct Channels {
channel0: Channel<Channel0>, channel0: Channel<Channel0>,
@ -122,7 +98,7 @@ impl Channels {
pub fn get_center(&mut self, channel: usize) -> ElectricPotential { pub fn get_center(&mut self, channel: usize) -> ElectricPotential {
match self.channel_state(channel).center { match self.channel_state(channel).center {
CenterPoint::Vref => CenterPoint::Vref =>
self.adc_read(channel, PinsAdcReadTarget::VREF, 8), self.read_vref(channel),
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point.into()), ElectricPotential::new::<volt>(center_point.into()),
} }
@ -135,13 +111,16 @@ impl Channels {
} }
pub fn get_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_i(&mut self, channel: usize) -> ElectricCurrent {
let i_set = self.channel_state(channel).i_set; let center_point = self.get_center(channel);
i_set let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = self.get_dac(channel);
let i_tec = (voltage - center_point) / (10.0 * r_sense);
i_tec
} }
/// i_set DAC /// i_set DAC
fn set_dac(&mut self, channel: usize, voltage: ElectricPotential) -> ElectricPotential { fn set_dac(&mut self, channel: usize, voltage: ElectricPotential) -> ElectricPotential {
let value = ((voltage / DAC_OUT_V_MAX).get::<ratio>() * (ad5680::MAX_VALUE as f64)) as u32 ; let value = ((voltage / ElectricPotential::new::<volt>(DAC_OUT_V_MAX)).get::<ratio>() * (ad5680::MAX_VALUE as f64)) as u32 ;
match channel { match channel {
0 => self.channel0.dac.set(value).unwrap(), 0 => self.channel0.dac.set(value).unwrap(),
1 => self.channel1.dac.set(value).unwrap(), 1 => self.channel1.dac.set(value).unwrap(),
@ -151,8 +130,7 @@ impl Channels {
voltage voltage
} }
pub fn set_i(&mut self, channel: usize, i_set: ElectricCurrent) -> ElectricCurrent { pub fn set_i(&mut self, channel: usize, i_tec: ElectricCurrent) -> ElectricCurrent {
let i_set = i_set.min(MAX_TEC_I).max(-MAX_TEC_I);
let vref_meas = match channel.into() { let vref_meas = match channel.into() {
0 => self.channel0.vref_meas, 0 => self.channel0.vref_meas,
1 => self.channel1.vref_meas, 1 => self.channel1.vref_meas,
@ -160,119 +138,38 @@ impl Channels {
}; };
let center_point = vref_meas; let center_point = vref_meas;
let r_sense = ElectricalResistance::new::<ohm>(R_SENSE); let r_sense = ElectricalResistance::new::<ohm>(R_SENSE);
let voltage = i_set * 10.0 * r_sense + center_point; let voltage = i_tec * 10.0 * r_sense + center_point;
let voltage = self.set_dac(channel, voltage); let voltage = self.set_dac(channel, voltage);
let i_set = (voltage - center_point) / (10.0 * r_sense); let i_tec = (voltage - center_point) / (10.0 * r_sense);
self.channel_state(channel).i_set = i_set; i_tec
i_set
} }
/// AN4073: ADC Reading Dispersion can be reduced through Averaging pub fn read_dac_feedback(&mut self, channel: usize) -> ElectricPotential {
pub fn adc_read(&mut self, channel: usize, adc_read_target: PinsAdcReadTarget, avg_pt: u16) -> ElectricPotential {
let mut sample: u32 = 0;
match channel { match channel {
0 => { 0 => {
sample = match adc_read_target { let sample = self.pins_adc.convert(
PinsAdcReadTarget::VREF => { &self.channel0.dac_feedback_pin,
match &self.channel0.vref_pin { stm32f4xx_hal::adc::config::SampleTime::Cycles_480
Channel0VRef::Analog(vref_pin) => { );
for _ in (0..avg_pt).rev() { let mv = self.pins_adc.sample_to_millivolts(sample);
sample += self
.pins_adc
.convert(vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
},
Channel0VRef::Disabled(_) => {2048 as u32}
}
}
PinsAdcReadTarget::DacVfb => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.dac_feedback_pin,stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::ITec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::VTec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel0.tec_u_meas_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
};
let mv = self.pins_adc.sample_to_millivolts(sample as u16);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
1 => { 1 => {
sample = match adc_read_target { let sample = self.pins_adc.convert(
PinsAdcReadTarget::VREF => { &self.channel1.dac_feedback_pin,
match &self.channel1.vref_pin { stm32f4xx_hal::adc::config::SampleTime::Cycles_480
Channel1VRef::Analog(vref_pin) => { );
for _ in (0..avg_pt).rev() { let mv = self.pins_adc.sample_to_millivolts(sample);
sample += self
.pins_adc
.convert(vref_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
},
Channel1VRef::Disabled(_) => {2048 as u32}
}
}
PinsAdcReadTarget::DacVfb => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.dac_feedback_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::ITec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.itec_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
PinsAdcReadTarget::VTec => {
for _ in (0..avg_pt).rev() {
sample += self
.pins_adc
.convert(&self.channel1.tec_u_meas_pin, stm32f4xx_hal::adc::config::SampleTime::Cycles_480)
as u32;
}
sample / avg_pt as u32
}
};
let mv = self.pins_adc.sample_to_millivolts(sample as u16);
ElectricPotential::new::<millivolt>(mv as f64) ElectricPotential::new::<millivolt>(mv as f64)
} }
_ => unreachable!() _ => unreachable!(),
} }
} }
pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential { pub fn read_dac_feedback_until_stable(&mut self, channel: usize, tolerance: ElectricPotential) -> ElectricPotential {
let mut prev = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1); let mut prev = self.read_dac_feedback(channel);
loop { loop {
let current = self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1); let current = self.read_dac_feedback(channel);
if (current - prev).abs() < tolerance { if (current - prev).abs() < tolerance {
return current; return current;
} }
@ -280,6 +177,73 @@ impl Channels {
} }
} }
pub fn read_itec(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
let sample = self.pins_adc.convert(
&self.channel0.itec_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
1 => {
let sample = self.pins_adc.convert(
&self.channel1.itec_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
_ => unreachable!(),
}
}
/// should be 1.5V
pub fn read_vref(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
let sample = self.pins_adc.convert(
&self.channel0.vref_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
1 => {
let sample = self.pins_adc.convert(
&self.channel1.vref_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
_ => unreachable!(),
}
}
pub fn read_tec_u_meas(&mut self, channel: usize) -> ElectricPotential {
match channel {
0 => {
let sample = self.pins_adc.convert(
&self.channel0.tec_u_meas_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
1 => {
let sample = self.pins_adc.convert(
&self.channel1.tec_u_meas_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480
);
let mv = self.pins_adc.sample_to_millivolts(sample);
ElectricPotential::new::<millivolt>(mv as f64)
}
_ => unreachable!(),
}
}
/// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output. /// Calibrates the DAC output to match vref of the MAX driver to reduce zero-current offset of the MAX driver output.
/// ///
/// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current. /// The thermostat DAC applies a control voltage signal to the CTLI pin of MAX driver chip to control its output current.
@ -327,7 +291,7 @@ impl Channels {
best_error = error; best_error = error;
start_value = prev_value; start_value = prev_value;
let vref = (value as f64 / ad5680::MAX_VALUE as f64) * DAC_OUT_V_MAX; let vref = (value as f64 / ad5680::MAX_VALUE as f64) * ElectricPotential::new::<volt>(DAC_OUT_V_MAX);
match channel { match channel {
0 => self.channel0.vref_meas = vref, 0 => self.channel0.vref_meas = vref,
1 => self.channel1.vref_meas = vref, 1 => self.channel1.vref_meas = vref,
@ -387,32 +351,32 @@ impl Channels {
} }
} }
pub fn get_max_v(&mut self, channel: usize) -> (ElectricPotential, ElectricPotential) { pub fn get_max_v(&mut self, channel: usize) -> ElectricPotential {
let max = 4.0 * ElectricPotential::new::<volt>(3.3); let max = 4.0 * ElectricPotential::new::<volt>(3.3);
let duty = self.get_pwm(channel, PwmPin::MaxV); let duty = self.get_pwm(channel, PwmPin::MaxV);
(duty * max, MAX_TEC_V) duty * max
} }
pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { pub fn get_max_i_pos(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let duty = self.get_pwm(channel, PwmPin::MaxIPos); let duty = self.get_pwm(channel, PwmPin::MaxIPos);
(duty * max, MAX_TEC_I) (duty * max, max)
} }
pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) { pub fn get_max_i_neg(&mut self, channel: usize) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let duty = self.get_pwm(channel, PwmPin::MaxINeg); let duty = self.get_pwm(channel, PwmPin::MaxINeg);
(duty * max, MAX_TEC_I) (duty * max, max)
} }
// Get current passing through TEC // Get current passing through TEC
pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent { pub fn get_tec_i(&mut self, channel: usize) -> ElectricCurrent {
(self.adc_read(channel, PinsAdcReadTarget::ITec, 16) - self.adc_read(channel, PinsAdcReadTarget::VREF, 16)) / ElectricalResistance::new::<ohm>(0.4) (self.read_itec(channel) - self.read_vref(channel)) / ElectricalResistance::new::<ohm>(0.4)
} }
// Get voltage across TEC // Get voltage across TEC
pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential { pub fn get_tec_v(&mut self, channel: usize) -> ElectricPotential {
(self.adc_read(channel, PinsAdcReadTarget::VTec, 16) - ElectricPotential::new::<volt>(1.5)) * 4.0 (self.read_tec_u_meas(channel) - ElectricPotential::new::<volt>(1.5)) * 4.0
} }
fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 { fn set_pwm(&mut self, channel: usize, pin: PwmPin, duty: f64) -> f64 {
@ -444,28 +408,28 @@ impl Channels {
pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) { pub fn set_max_v(&mut self, channel: usize, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) {
let max = 4.0 * ElectricPotential::new::<volt>(3.3); let max = 4.0 * ElectricPotential::new::<volt>(3.3);
let duty = (max_v.min(MAX_TEC_V).max(ElectricPotential::zero()) / max).get::<ratio>(); let duty = (max_v / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxV, duty); let duty = self.set_pwm(channel, PwmPin::MaxV, duty);
(duty * max, max) (duty * max, max)
} }
pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { pub fn set_max_i_pos(&mut self, channel: usize, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let duty = (max_i_pos.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>(); let duty = (max_i_pos / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty); let duty = self.set_pwm(channel, PwmPin::MaxIPos, duty);
(duty * max, max) (duty * max, max)
} }
pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { pub fn set_max_i_neg(&mut self, channel: usize, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) {
let max = ElectricCurrent::new::<ampere>(3.0); let max = ElectricCurrent::new::<ampere>(3.0);
let duty = (max_i_neg.min(MAX_TEC_I).max(ElectricCurrent::zero()) / max).get::<ratio>(); let duty = (max_i_neg / max).get::<ratio>();
let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty); let duty = self.set_pwm(channel, PwmPin::MaxINeg, duty);
(duty * max, max) (duty * max, max)
} }
fn report(&mut self, channel: usize) -> Report { fn report(&mut self, channel: usize) -> Report {
let i_set = self.get_i(channel); let i_set = self.get_i(channel);
let i_tec = self.adc_read(channel, PinsAdcReadTarget::ITec, 16); let i_tec = self.read_itec(channel);
let tec_i = self.get_tec_i(channel); let tec_i = self.get_tec_i(channel);
let dac_value = self.get_dac(channel); let dac_value = self.get_dac(channel);
let state = self.channel_state(channel); let state = self.channel_state(channel);
@ -481,7 +445,7 @@ impl Channels {
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set,
dac_value, dac_value,
dac_feedback: self.adc_read(channel, PinsAdcReadTarget::DacVfb, 1), dac_feedback: self.read_dac_feedback(channel),
i_tec, i_tec,
tec_i, tec_i,
tec_u_meas: self.get_tec_v(channel), tec_u_meas: self.get_tec_v(channel),
@ -518,8 +482,8 @@ impl Channels {
PwmSummary { PwmSummary {
channel, channel,
center: CenterPointJson(self.channel_state(channel).center.clone()), center: CenterPointJson(self.channel_state(channel).center.clone()),
i_set: (self.get_i(channel), MAX_TEC_I).into(), i_set: (self.get_i(channel), ElectricCurrent::new::<ampere>(3.0)).into(),
max_v: self.get_max_v(channel).into(), max_v: (self.get_max_v(channel), ElectricPotential::new::<volt>(5.0)).into(),
max_i_pos: self.get_max_i_pos(channel).into(), max_i_pos: self.get_max_i_pos(channel).into(),
max_i_neg: self.get_max_i_neg(channel).into(), max_i_neg: self.get_max_i_neg(channel).into(),
} }
@ -560,9 +524,9 @@ impl Channels {
serde_json_core::to_vec(&summaries) serde_json_core::to_vec(&summaries)
} }
pub fn current_abs_max_tec_i(&mut self) -> ElectricCurrent { pub fn current_abs_max_tec_i(&mut self) -> f64 {
max_by(self.get_tec_i(0).abs(), max_by(self.get_tec_i(0).abs().get::<ampere>(),
self.get_tec_i(1).abs(), self.get_tec_i(1).abs().get::<ampere>(),
|a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal)) |a, b| a.partial_cmp(b).unwrap_or(core::cmp::Ordering::Equal))
} }
} }

View File

@ -13,6 +13,7 @@ use super::{
PwmPin, PwmPin,
ShParameter ShParameter
}, },
leds::Leds,
ad7172, ad7172,
CHANNEL_CONFIG_KEY, CHANNEL_CONFIG_KEY,
channels::{ channels::{
@ -175,13 +176,13 @@ impl Handler {
Ok(Handler::Handled) Ok(Handler::Handled)
} }
fn engage_pid (socket: &mut TcpSocket, channels: &mut Channels, channel: usize) -> Result<Handler, Error> { fn engage_pid (socket: &mut TcpSocket, channels: &mut Channels, leds: &mut Leds, channel: usize) -> Result<Handler, Error> {
channels.channel_state(channel).pid_engaged = true; channels.channel_state(channel).pid_engaged = true;
send_line(socket, b"{}"); send_line(socket, b"{}");
Ok(Handler::Handled) Ok(Handler::Handled)
} }
fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> { fn set_pwm (socket: &mut TcpSocket, channels: &mut Channels, leds: &mut Leds, channel: usize, pin: PwmPin, value: f64) -> Result<Handler, Error> {
match pin { match pin {
PwmPin::ISet => { PwmPin::ISet => {
channels.channel_state(channel).pid_engaged = false; channels.channel_state(channel).pid_engaged = false;
@ -207,11 +208,11 @@ impl Handler {
} }
fn set_center_point(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, center: CenterPoint) -> Result<Handler, Error> { fn set_center_point(socket: &mut TcpSocket, channels: &mut Channels, channel: usize, center: CenterPoint) -> Result<Handler, Error> {
let i_set = channels.get_i(channel); let i_tec = channels.get_i(channel);
let state = channels.channel_state(channel); let state = channels.channel_state(channel);
state.center = center; state.center = center;
if !state.pid_engaged { if !state.pid_engaged {
channels.set_i(channel, i_set); channels.set_i(channel, i_tec);
} }
send_line(socket, b"{}"); send_line(socket, b"{}");
Ok(Handler::Handled) Ok(Handler::Handled)
@ -412,7 +413,7 @@ impl Handler {
} }
} }
pub fn handle_command(command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl, hwrev: HWRev) -> Result<Self, Error> { pub fn handle_command(command: Command, socket: &mut TcpSocket, channels: &mut Channels, session: &Session, leds: &mut Leds, store: &mut FlashStore, ipv4_config: &mut Ipv4Config, fan_ctrl: &mut FanCtrl, hwrev: HWRev) -> Result<Self, Error> {
match command { match command {
Command::Quit => Ok(Handler::CloseSocket), Command::Quit => Ok(Handler::CloseSocket),
Command::Reporting(_reporting) => Handler::reporting(socket), Command::Reporting(_reporting) => Handler::reporting(socket),
@ -423,8 +424,8 @@ impl Handler {
Command::Show(ShowCommand::SteinhartHart) => Handler::show_steinhart_hart(socket, channels), Command::Show(ShowCommand::SteinhartHart) => Handler::show_steinhart_hart(socket, channels),
Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels), Command::Show(ShowCommand::PostFilter) => Handler::show_post_filter(socket, channels),
Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config), Command::Show(ShowCommand::Ipv4) => Handler::show_ipv4(socket, ipv4_config),
Command::PwmPid { channel } => Handler::engage_pid(socket, channels, channel), Command::PwmPid { channel } => Handler::engage_pid(socket, channels, leds, channel),
Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, channel, pin, value), Command::Pwm { channel, pin, value } => Handler::set_pwm(socket, channels, leds, channel, pin, value),
Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center), Command::CenterPoint { channel, center } => Handler::set_center_point(socket, channels, channel, center),
Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value), Command::Pid { channel, parameter, value } => Handler::set_pid(socket, channels, channel, parameter, value),
Command::SteinhartHart { channel, parameter, value } => Handler::set_steinhart_hart(socket, channels, channel, parameter, value), Command::SteinhartHart { channel, parameter, value } => Handler::set_steinhart_hart(socket, channels, channel, parameter, value),

View File

@ -1,4 +1,3 @@
use num_traits::Zero;
use serde::{Serialize, Deserialize}; use serde::{Serialize, Deserialize};
use uom::si::{ use uom::si::{
electric_potential::volt, electric_potential::volt,
@ -19,7 +18,6 @@ pub struct ChannelConfig {
pid: pid::Parameters, pid: pid::Parameters,
pid_target: f32, pid_target: f32,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent,
sh: steinhart_hart::Parameters, sh: steinhart_hart::Parameters,
pwm: PwmLimits, pwm: PwmLimits,
/// uses variant `PostFilter::Invalid` instead of `None` to save space /// uses variant `PostFilter::Invalid` instead of `None` to save space
@ -35,17 +33,11 @@ impl ChannelConfig {
.unwrap_or(PostFilter::Invalid); .unwrap_or(PostFilter::Invalid);
let state = channels.channel_state(channel); let state = channels.channel_state(channel);
let i_set = if state.pid_engaged {
ElectricCurrent::zero()
} else {
state.i_set
};
ChannelConfig { ChannelConfig {
center: state.center.clone(), center: state.center.clone(),
pid: state.pid.parameters.clone(), pid: state.pid.parameters.clone(),
pid_target: state.pid.target as f32, pid_target: state.pid.target as f32,
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set: i_set,
sh: state.sh.clone(), sh: state.sh.clone(),
pwm, pwm,
adc_postfilter, adc_postfilter,
@ -67,7 +59,6 @@ impl ChannelConfig {
adc_postfilter => Some(adc_postfilter), adc_postfilter => Some(adc_postfilter),
}; };
let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter); let _ = channels.adc.set_postfilter(channel as u8, adc_postfilter);
let _ = channels.set_i(channel, self.i_set);
} }
} }
@ -80,7 +71,7 @@ struct PwmLimits {
impl PwmLimits { impl PwmLimits {
pub fn new(channels: &mut Channels, channel: usize) -> Self { pub fn new(channels: &mut Channels, channel: usize) -> Self {
let (max_v, _) = channels.get_max_v(channel); let max_v = channels.get_max_v(channel);
let (max_i_pos, _) = channels.get_max_i_pos(channel); let (max_i_pos, _) = channels.get_max_i_pos(channel);
let (max_i_neg, _) = channels.get_max_i_neg(channel); let (max_i_neg, _) = channels.get_max_i_neg(channel);
PwmLimits { PwmLimits {

View File

@ -1,4 +1,3 @@
use core::arch::asm;
use cortex_m_rt::pre_init; use cortex_m_rt::pre_init;
use stm32f4xx_hal::stm32::{RCC, SYSCFG}; use stm32f4xx_hal::stm32::{RCC, SYSCFG};

View File

@ -4,18 +4,17 @@ use stm32f4xx_hal::{
pwm::{self, PwmChannels}, pwm::{self, PwmChannels},
pac::TIM8, pac::TIM8,
}; };
use uom::si::{
f64::ElectricCurrent,
electric_current::ampere,
};
use crate::{ use crate::{
hw_rev::HWSettings, hw_rev::HWSettings,
command_handler::JsonBuffer, command_handler::JsonBuffer,
channels::MAX_TEC_I,
}; };
pub type FanPin = PwmChannels<TIM8, pwm::C4>; pub type FanPin = PwmChannels<TIM8, pwm::C4>;
// as stated in the schematics
const MAX_TEC_I: f32 = 3.0;
const MAX_USER_FAN_PWM: f32 = 100.0; const MAX_USER_FAN_PWM: f32 = 100.0;
const MIN_USER_FAN_PWM: f32 = 1.0; const MIN_USER_FAN_PWM: f32 = 1.0;
@ -51,10 +50,10 @@ impl FanCtrl {
fan_ctrl fan_ctrl
} }
pub fn cycle(&mut self, abs_max_tec_i: ElectricCurrent) { pub fn cycle(&mut self, abs_max_tec_i: f32) {
self.abs_max_tec_i = abs_max_tec_i.get::<ampere>() as f32; self.abs_max_tec_i = abs_max_tec_i;
if self.fan_auto && self.hw_settings.fan_available { if self.fan_auto && self.hw_settings.fan_available {
let scaled_current = self.abs_max_tec_i / MAX_TEC_I.get::<ampere>() as f32; let scaled_current = self.abs_max_tec_i / MAX_TEC_I;
// do not limit upper bound, as it will be limited in the set_pwm() // do not limit upper bound, as it will be limited in the set_pwm()
let pwm = (MAX_USER_FAN_PWM * (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c)) as u32; let pwm = (MAX_USER_FAN_PWM * (scaled_current * (scaled_current * self.k_a + self.k_b) + self.k_c)) as u32;
self.set_pwm(pwm); self.set_pwm(pwm);

View File

@ -1,10 +1,11 @@
#![cfg_attr(not(test), no_std)] #![cfg_attr(not(test), no_std)]
#![cfg_attr(not(test), no_main)] #![cfg_attr(not(test), no_main)]
#![feature(maybe_uninit_extra, asm)]
#![cfg_attr(test, allow(unused))] #![cfg_attr(test, allow(unused))]
// TODO: #![deny(warnings, unused)] // TODO: #![deny(warnings, unused)]
#[cfg(not(any(feature = "semihosting", test)))] #[cfg(not(any(feature = "semihosting", test)))]
use panic_halt as _; use panic_abort as _;
#[cfg(all(feature = "semihosting", not(test)))] #[cfg(all(feature = "semihosting", not(test)))]
use panic_semihosting as _; use panic_semihosting as _;
@ -185,7 +186,7 @@ fn main() -> ! {
server.for_each(|_, session| session.set_report_pending(channel.into())); server.for_each(|_, session| session.set_report_pending(channel.into()));
} }
fan_ctrl.cycle(channels.current_abs_max_tec_i()); fan_ctrl.cycle(channels.current_abs_max_tec_i() as f32);
if channels.pid_engaged() { if channels.pid_engaged() {
leds.g3.on(); leds.g3.on();
@ -216,7 +217,7 @@ fn main() -> ! {
// Do nothing and feed more data to the line reader in the next loop cycle. // Do nothing and feed more data to the line reader in the next loop cycle.
Ok(SessionInput::Nothing) => {} Ok(SessionInput::Nothing) => {}
Ok(SessionInput::Command(command)) => { Ok(SessionInput::Command(command)) => {
match Handler::handle_command(command, &mut socket, &mut channels, session, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) { match Handler::handle_command(command, &mut socket, &mut channels, session, &mut leds, &mut store, &mut ipv4_config, &mut fan_ctrl, hwrev) {
Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip), Ok(Handler::NewIPV4(ip)) => new_ipv4_config = Some(ip),
Ok(Handler::Handled) => {}, Ok(Handler::Handled) => {},
Ok(Handler::CloseSocket) => socket.close(), Ok(Handler::CloseSocket) => socket.close(),

View File

@ -54,13 +54,15 @@ impl Controller {
// + x0 * (kp + ki + kd) // + x0 * (kp + ki + kd)
// - x1 * (kp + 2kd) // - x1 * (kp + 2kd)
// + x2 * kd // + x2 * kd
// + kp * (u0 - u1)
// y0 = clip(y0', ymin, ymax) // y0 = clip(y0', ymin, ymax)
pub fn update(&mut self, input: f64) -> f64 { pub fn update(&mut self, input: f64) -> f64 {
let mut output: f64 = self.y1 - self.target * f64::from(self.parameters.ki) let mut output: f64 = self.y1 - self.target * f64::from(self.parameters.ki)
+ input * f64::from(self.parameters.kp + self.parameters.ki + self.parameters.kd) + input * f64::from(self.parameters.kp + self.parameters.ki + self.parameters.kd)
- self.x1 * f64::from(self.parameters.kp + 2.0 * self.parameters.kd) - self.x1 * f64::from(self.parameters.kp + 2.0 * self.parameters.kd)
+ self.x2 * f64::from(self.parameters.kd); + self.x2 * f64::from(self.parameters.kd)
+ f64::from(self.parameters.kp) * (self.target - self.u1);
if output < self.parameters.output_min.into() { if output < self.parameters.output_min.into() {
output = self.parameters.output_min.into(); output = self.parameters.output_min.into();
} }

View File

@ -66,31 +66,21 @@ pub trait ChannelPins {
type TecUMeasPin; type TecUMeasPin;
} }
pub enum Channel0VRef {
Analog(PA0<Analog>),
Disabled(PA0<Input<Floating>>),
}
impl ChannelPins for Channel0 { impl ChannelPins for Channel0 {
type DacSpi = Dac0Spi; type DacSpi = Dac0Spi;
type DacSync = PE4<Output<PushPull>>; type DacSync = PE4<Output<PushPull>>;
type Shdn = PE10<Output<PushPull>>; type Shdn = PE10<Output<PushPull>>;
type VRefPin = Channel0VRef; type VRefPin = PA0<Analog>;
type ItecPin = PA6<Analog>; type ItecPin = PA6<Analog>;
type DacFeedbackPin = PA4<Analog>; type DacFeedbackPin = PA4<Analog>;
type TecUMeasPin = PC2<Analog>; type TecUMeasPin = PC2<Analog>;
} }
pub enum Channel1VRef {
Analog(PA3<Analog>),
Disabled(PA3<Input<Floating>>),
}
impl ChannelPins for Channel1 { impl ChannelPins for Channel1 {
type DacSpi = Dac1Spi; type DacSpi = Dac1Spi;
type DacSync = PF6<Output<PushPull>>; type DacSync = PF6<Output<PushPull>>;
type Shdn = PE15<Output<PushPull>>; type Shdn = PE15<Output<PushPull>>;
type VRefPin = Channel1VRef; type VRefPin = PA3<Analog>;
type ItecPin = PB0<Analog>; type ItecPin = PB0<Analog>;
type DacFeedbackPin = PA5<Analog>; type DacFeedbackPin = PA5<Analog>;
type TecUMeasPin = PC3<Analog>; type TecUMeasPin = PC3<Analog>;
@ -160,17 +150,13 @@ impl Pins {
gpioe.pe13, gpioe.pe14 gpioe.pe13, gpioe.pe14
); );
let hwrev = HWRev::detect_hw_rev(&HWRevPins {hwrev0: gpiod.pd0, hwrev1: gpiod.pd1,
hwrev2: gpiod.pd2, hwrev3: gpiod.pd3});
let hw_settings = hwrev.settings();
let (dac0_spi, dac0_sync) = Self::setup_dac0( let (dac0_spi, dac0_sync) = Self::setup_dac0(
clocks, spi4, clocks, spi4,
gpioe.pe2, gpioe.pe4, gpioe.pe6 gpioe.pe2, gpioe.pe4, gpioe.pe6
); );
let mut shdn0 = gpioe.pe10.into_push_pull_output(); let mut shdn0 = gpioe.pe10.into_push_pull_output();
let _ = shdn0.set_low(); let _ = shdn0.set_low();
let vref0_pin = if hwrev.major > 2 {Channel0VRef::Analog(gpioa.pa0.into_analog())} else {Channel0VRef::Disabled(gpioa.pa0)}; let vref0_pin = gpioa.pa0.into_analog();
let itec0_pin = gpioa.pa6.into_analog(); let itec0_pin = gpioa.pa6.into_analog();
let dac_feedback0_pin = gpioa.pa4.into_analog(); let dac_feedback0_pin = gpioa.pa4.into_analog();
let tec_u_meas0_pin = gpioc.pc2.into_analog(); let tec_u_meas0_pin = gpioc.pc2.into_analog();
@ -190,7 +176,7 @@ impl Pins {
); );
let mut shdn1 = gpioe.pe15.into_push_pull_output(); let mut shdn1 = gpioe.pe15.into_push_pull_output();
let _ = shdn1.set_low(); let _ = shdn1.set_low();
let vref1_pin = if hwrev.major > 2 {Channel1VRef::Analog(gpioa.pa3.into_analog())} else {Channel1VRef::Disabled(gpioa.pa3)}; let vref1_pin = gpioa.pa3.into_analog();
let itec1_pin = gpiob.pb0.into_analog(); let itec1_pin = gpiob.pb0.into_analog();
let dac_feedback1_pin = gpioa.pa5.into_analog(); let dac_feedback1_pin = gpioa.pa5.into_analog();
let tec_u_meas1_pin = gpioc.pc3.into_analog(); let tec_u_meas1_pin = gpioc.pc3.into_analog();
@ -212,6 +198,10 @@ impl Pins {
channel1, channel1,
}; };
let hwrev = HWRev::detect_hw_rev(&HWRevPins {hwrev0: gpiod.pd0, hwrev1: gpiod.pd1,
hwrev2: gpiod.pd2, hwrev3: gpiod.pd3});
let hw_settings = hwrev.settings();
let leds = Leds::new(gpiod.pd9, gpiod.pd10.into_push_pull_output(), gpiod.pd11.into_push_pull_output()); let leds = Leds::new(gpiod.pd9, gpiod.pd10.into_push_pull_output(), gpiod.pd11.into_push_pull_output());
let eeprom_scl = gpiob.pb8.into_alternate().set_open_drain(); let eeprom_scl = gpiob.pb8.into_alternate().set_open_drain();

View File

@ -1,3 +1,4 @@
use core::mem::MaybeUninit;
use smoltcp::{ use smoltcp::{
iface::EthernetInterface, iface::EthernetInterface,
socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef}, socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef},
@ -12,18 +13,6 @@ pub struct SocketState<S> {
state: S, state: S,
} }
impl<'a, S: Default> SocketState<S>{
fn new(sockets: &mut SocketSet<'a>, tcp_rx_storage: &'a mut [u8; TCP_RX_BUFFER_SIZE], tcp_tx_storage: &'a mut [u8; TCP_TX_BUFFER_SIZE]) -> SocketState<S> {
let tcp_rx_buffer = TcpSocketBuffer::new(&mut tcp_rx_storage[..]);
let tcp_tx_buffer = TcpSocketBuffer::new(&mut tcp_tx_storage[..]);
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
SocketState::<S> {
handle: sockets.add(tcp_socket),
state: S::default()
}
}
}
/// Number of server sockets and therefore concurrent client /// Number of server sockets and therefore concurrent client
/// sessions. Many data structures in `Server::run()` correspond to /// sessions. Many data structures in `Server::run()` correspond to
/// this const. /// this const.
@ -46,27 +35,28 @@ impl<'a, 'b, S: Default> Server<'a, 'b, S> {
where where
F: FnOnce(&mut Server<'a, '_, S>), F: FnOnce(&mut Server<'a, '_, S>),
{ {
macro_rules! create_rtx_storage {
($rx_storage:ident, $tx_storage:ident) => {
let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE];
}
}
create_rtx_storage!(tcp_rx_storage0, tcp_tx_storage0);
create_rtx_storage!(tcp_rx_storage1, tcp_tx_storage1);
create_rtx_storage!(tcp_rx_storage2, tcp_tx_storage2);
create_rtx_storage!(tcp_rx_storage3, tcp_tx_storage3);
let mut sockets_storage: [_; SOCKET_COUNT] = Default::default(); let mut sockets_storage: [_; SOCKET_COUNT] = Default::default();
let mut sockets = SocketSet::new(&mut sockets_storage[..]); let mut sockets = SocketSet::new(&mut sockets_storage[..]);
let mut states: [SocketState<S>; SOCKET_COUNT] = unsafe { MaybeUninit::uninit().assume_init() };
let states: [SocketState<S>; SOCKET_COUNT] = [ macro_rules! create_socket {
SocketState::<S>::new(&mut sockets, &mut tcp_rx_storage0, &mut tcp_tx_storage0), ($set:ident, $rx_storage:ident, $tx_storage:ident, $target:expr) => {
SocketState::<S>::new(&mut sockets, &mut tcp_rx_storage1, &mut tcp_tx_storage1), let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE];
SocketState::<S>::new(&mut sockets, &mut tcp_rx_storage2, &mut tcp_tx_storage2), let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE];
SocketState::<S>::new(&mut sockets, &mut tcp_rx_storage3, &mut tcp_tx_storage3), let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]);
]; let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]);
let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer);
$target = $set.add(tcp_socket);
}
}
create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, states[0].handle);
create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, states[1].handle);
create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, states[2].handle);
create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, states[3].handle);
for state in &mut states {
state.state = S::default();
}
let mut server = Server { let mut server = Server {
states, states,

View File

@ -22,7 +22,7 @@ pub struct Parameters {
} }
impl Parameters { impl Parameters {
/// Perform the resistance to temperature conversion. /// Perform the voltage to temperature conversion.
pub fn get_temperature(&self, r: ElectricalResistance) -> ThermodynamicTemperature { pub fn get_temperature(&self, r: ElectricalResistance) -> ThermodynamicTemperature {
let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b.get::<kelvin>(); let inv_temp = 1.0 / self.t0.get::<kelvin>() + (r / self.r0).get::<ratio>().ln() / self.b.get::<kelvin>();
ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp) ThermodynamicTemperature::new::<kelvin>(1.0 / inv_temp)