Compare commits

..

3 Commits

Author SHA1 Message Date
Egor Savkin 7015538b43 Clean up endings in commands
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-03-22 17:28:47 +08:00
Egor Savkin 60118a56c8 Accept ending whitespace in commands
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-03-22 17:21:38 +08:00
topquark12 211e0fa1bd fix whitespace error 2023-03-22 17:20:09 +08:00
17 changed files with 297 additions and 339 deletions

29
Cargo.lock generated
View File

@ -204,6 +204,26 @@ dependencies = [
"void", "void",
] ]
[[package]]
name = "format-bytes"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48942366ef93975da38e175ac9e10068c6fc08ca9e85930d4f098f4d5b14c2fd"
dependencies = [
"format-bytes-macros",
]
[[package]]
name = "format-bytes-macros"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "203aadebefcc73d12038296c228eabf830f99cba991b0032adf20e9fa6ce7e4f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.3" version = "0.12.3"
@ -327,10 +347,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"
@ -558,12 +578,13 @@ dependencies = [
"cortex-m-log", "cortex-m-log",
"cortex-m-rt 0.6.13", "cortex-m-rt 0.6.13",
"eeprom24x", "eeprom24x",
"format-bytes",
"heapless", "heapless",
"log", "log",
"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"
@ -38,6 +38,9 @@ heapless = "0.5"
serde-json-core = "0.1" serde-json-core = "0.1"
sfkv = "0.1" sfkv = "0.1"
[dev-dependencies]
format-bytes = "0.3.0"
[features] [features]
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"] semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]

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
@ -184,7 +184,7 @@ postfilter rate can be tuned with the `postfilter` command.
- Connect TEC module device 1 to TEC1- and TEC1+. - Connect TEC module device 1 to TEC1- and TEC1+.
- The GND pin is for shielding not for sinking TEC module currents. - The GND pin is for shielding not for sinking TEC module currents.
When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to cool down with a positive software current set point, and heat up with a negative current set point. When using a TEC module with the Thermostat, the Thermostat expects the thermal load (where the thermistor is located) to heat up with a positive software current set point, and cool down with a negative current set point.
Testing heat flow direction with a low set current is recommended before installation of the TEC module. Testing heat flow direction with a low set current is recommended before installation of the TEC module.
@ -258,12 +258,13 @@ with the following keys.
| Key | Unit | Description | | Key | Unit | Description |
| --- | :---: | --- | | --- | :---: | --- |
| `channel` | Integer | Channel `0`, or `1` | | `channel` | Integer | Channel `0`, or `1` |
| `time` | Seconds | Temperature measurement time | | `time` | Milliseconds | Temperature measurement time |
| `adc` | Volts | AD7172 input | | `adc` | Volts | AD7172 input |
| `sens` | Ohms | Thermistor resistance derived from `adc` | | `sens` | Ohms | Thermistor resistance derived from `adc` |
| `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`.

View File

@ -3,11 +3,11 @@
"mozilla-overlay": { "mozilla-overlay": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1690536331, "lastModified": 1638887313,
"narHash": "sha256-aRIf2FB2GTdfF7gl13WyETmiV/J7EhBGkSWXfZvlxcA=", "narHash": "sha256-FMYV6rVtvSIfthgC1sK1xugh3y7muoQcvduMdriz4ag=",
"owner": "mozilla", "owner": "mozilla",
"repo": "nixpkgs-mozilla", "repo": "nixpkgs-mozilla",
"rev": "db89c8707edcffefcd8e738459d511543a339ff5", "rev": "7c1e8b1dd6ed0043fb4ee0b12b815256b0b9de6f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,16 +18,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1691421349, "lastModified": 1641870998,
"narHash": "sha256-RRJyX0CUrs4uW4gMhd/X4rcDG8PTgaaCQM5rXEJOx6g=", "narHash": "sha256-6HkxR2WZsm37VoQS7jgp6Omd71iw6t1kP8bDbaqCDuI=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "011567f35433879aae5024fc6ec53f2a0568a6c4", "rev": "386234e2a61e1e8acf94dfa3a3d3ca19a6776efb",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.05", "ref": "nixos-21.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View File

@ -1,15 +1,15 @@
{ {
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-21.11;
inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; }; inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
outputs = { self, nixpkgs, mozilla-overlay }: outputs = { self, nixpkgs, mozilla-overlay }:
let let
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
rustManifest = pkgs.fetchurl { rustManifest = pkgs.fetchurl {
url = "https://static.rust-lang.org/dist/2022-12-15/channel-rust-stable.toml"; url = "https://static.rust-lang.org/dist/2021-10-26/channel-rust-nightly.toml";
hash = "sha256-S7epLlflwt0d1GZP44u5Xosgf6dRrmr8xxC+Ml2Pq7c="; sha256 = "sha256-1hLbypXA+nuH7o3AHCokzSBZAvQxvef4x9+XxO3aBao=";
}; };
targets = [ targets = [
@ -22,12 +22,12 @@
inherit targets; inherit targets;
extensions = ["rust-src"]; extensions = ["rust-src"];
}; };
rust = rustChannelOfTargets "stable" null targets; rust = rustChannelOfTargets "nightly" null targets;
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform { rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
rustc = rust; rustc = rust;
cargo = rust; cargo = rust;
}); });
thermostat = rustPlatform.buildRustPackage { thermostat = rustPlatform.buildRustPackage rec {
name = "thermostat"; name = "thermostat";
version = "0.0.0"; version = "0.0.0";
@ -67,7 +67,9 @@
devShell.x86_64-linux = pkgs.mkShell { devShell.x86_64-linux = pkgs.mkShell {
name = "thermostat-dev-shell"; name = "thermostat-dev-shell";
buildInputs = with pkgs; [ buildInputs = with pkgs; [
rust openocd dfu-util rustPlatform.rust.rustc
rustPlatform.rust.cargo
openocd dfu-util
] ++ (with python3Packages; [ ] ++ (with python3Packages; [
numpy matplotlib numpy matplotlib
]); ]);

View File

@ -1,6 +1,5 @@
import socket import socket
import json import json
import logging
class CommandError(Exception): class CommandError(Exception):
pass pass
@ -9,14 +8,6 @@ class Client:
def __init__(self, host="192.168.1.26", port=23, timeout=None): def __init__(self, host="192.168.1.26", port=23, timeout=None):
self._socket = socket.create_connection((host, port), timeout) self._socket = socket.create_connection((host, port), timeout)
self._lines = [""] self._lines = [""]
self._check_zero_limits()
def _check_zero_limits(self):
pwm_report = self.get_pwm()
for pwm_channel in pwm_report:
for limit in ["max_i_neg", "max_i_pos", "max_v"]:
if pwm_channel[limit]["value"] == 0.0:
logging.warning("`{}` limit is set to zero on channel {}".format(limit, pwm_channel["channel"]))
def _read_line(self): def _read_line(self):
# read more lines # read more lines

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,
@ -28,10 +26,11 @@ pub struct ChannelState {
pub adc_calibration: ad7172::ChannelCalibration, pub adc_calibration: ad7172::ChannelCalibration,
pub adc_time: Instant, pub adc_time: Instant,
pub adc_interval: Duration, pub adc_interval: Duration,
/// VREF for the TEC (1.5V)
pub vref: ElectricPotential,
/// 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,
@ -45,9 +44,10 @@ impl ChannelState {
adc_time: Instant::from_secs(0), adc_time: Instant::from_secs(0),
// default: 10 Hz // default: 10 Hz
adc_interval: Duration::from_millis(100), adc_interval: Duration::from_millis(100),
// updated later with Channels.read_vref()
vref: ElectricPotential::new::<volt>(1.5),
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>,
@ -82,6 +58,7 @@ impl Channels {
let pwm = pins.pwm; let pwm = pins.pwm;
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm }; let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
for channel in 0..CHANNELS { for channel in 0..CHANNELS {
channels.channel_state(channel).vref = channels.read_vref(channel);
channels.calibrate_dac_value(channel); channels.calibrate_dac_value(channel);
channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0)); channels.set_i(channel, ElectricCurrent::new::<ampere>(0.0));
} }
@ -121,8 +98,11 @@ impl Channels {
/// calculate the TEC i_set centerpoint /// calculate the TEC i_set centerpoint
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), let vref = self.read_vref(channel);
self.channel_state(channel).vref = vref;
vref
},
CenterPoint::Override(center_point) => CenterPoint::Override(center_point) =>
ElectricPotential::new::<volt>(center_point.into()), ElectricPotential::new::<volt>(center_point.into()),
} }
@ -135,13 +115,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 +134,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 +142,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 +181,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 +295,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 +355,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 +412,29 @@ 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 vref = self.channel_state(channel).vref;
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);
@ -480,8 +449,9 @@ impl Channels {
.map(|temperature| temperature.get::<degree_celsius>()), .map(|temperature| temperature.get::<degree_celsius>()),
pid_engaged: state.pid_engaged, pid_engaged: state.pid_engaged,
i_set, i_set,
vref,
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),
@ -505,21 +475,12 @@ impl Channels {
serde_json_core::to_vec(&summaries) serde_json_core::to_vec(&summaries)
} }
pub fn pid_engaged(&mut self) -> bool {
for channel in 0..CHANNELS {
if self.channel_state(channel).pid_engaged {
return true;
}
}
false
}
fn pwm_summary(&mut self, channel: usize) -> PwmSummary { fn pwm_summary(&mut self, channel: usize) -> PwmSummary {
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 +521,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))
} }
} }
@ -577,6 +538,7 @@ pub struct Report {
temperature: Option<f64>, temperature: Option<f64>,
pid_engaged: bool, pid_engaged: bool,
i_set: ElectricCurrent, i_set: ElectricCurrent,
vref: ElectricPotential,
dac_value: ElectricPotential, dac_value: ElectricPotential,
dac_feedback: ElectricPotential, dac_feedback: ElectricPotential,
i_tec: ElectricPotential, i_tec: ElectricPotential,

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,16 +176,18 @@ 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;
leds.g3.on();
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;
leds.g3.off();
let current = ElectricCurrent::new::<ampere>(value); let current = ElectricCurrent::new::<ampere>(value);
channels.set_i(channel, current); channels.set_i(channel, current);
channels.power_up(channel); channels.power_up(channel);
@ -207,11 +210,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 +415,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 +426,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

@ -343,7 +343,6 @@ fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
} }
}, },
))(input)?; ))(input)?;
end(input)?;
Ok((input, result)) Ok((input, result))
}, },
value(Ok(Command::Show(ShowCommand::Pwm)), end) value(Ok(Command::Show(ShowCommand::Pwm)), end)
@ -362,7 +361,6 @@ fn center_point(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
Ok((input, value.map(|value| CenterPoint::Override(value as f32)))) Ok((input, value.map(|value| CenterPoint::Override(value as f32))))
} }
))(input)?; ))(input)?;
end(input)?;
Ok((input, center.map(|center| Command::CenterPoint { Ok((input, center.map(|center| Command::CenterPoint {
channel, channel,
center, center,
@ -465,7 +463,6 @@ fn load(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|input| { |input| {
let (input, _) = whitespace(input)?; let (input, _) = whitespace(input)?;
let (input, channel) = channel(input)?; let (input, channel) = channel(input)?;
let (input, _) = end(input)?;
Ok((input, Some(channel))) Ok((input, Some(channel)))
}, },
value(None, end) value(None, end)
@ -481,7 +478,6 @@ fn save(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|input| { |input| {
let (input, _) = whitespace(input)?; let (input, _) = whitespace(input)?;
let (input, channel) = channel(input)?; let (input, channel) = channel(input)?;
let (input, _) = end(input)?;
Ok((input, Some(channel))) Ok((input, Some(channel)))
}, },
value(None, end) value(None, end)
@ -585,22 +581,25 @@ fn fan_curve(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
} }
fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> { fn command(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
alt((value(Ok(Command::Quit), tag("quit")), let (input, result) =
load, alt((value(Ok(Command::Quit), tag("quit")),
save, load,
value(Ok(Command::Reset), tag("reset")), save,
ipv4, value(Ok(Command::Reset), tag("reset")),
map(report, Ok), ipv4,
pwm, map(report, Ok),
center_point, pwm,
pid, center_point,
steinhart_hart, pid,
postfilter, steinhart_hart,
value(Ok(Command::Dfu), tag("dfu")), postfilter,
fan, value(Ok(Command::Dfu), tag("dfu")),
fan_curve, fan,
value(Ok(Command::ShowHWRev), tag("hwrev")), fan_curve,
))(input) value(Ok(Command::ShowHWRev), tag("hwrev")),
))(input)?;
let (input, _) = end(input)?; // accept trailing whitespace(s) if present
Ok((input, result))
} }
impl Command { impl Command {
@ -619,46 +618,57 @@ impl Command {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::*; use super::*;
extern crate format_bytes;
use format_bytes::format_bytes;
macro_rules! parse_chkwhitespace {
($cmd: literal) => {{
let command = Command::parse($cmd);
let command_w = Command::parse(&format_bytes!(b"{}\r\t \r\n\n\t ", $cmd));
assert_eq!(command, command_w);
command
}};
}
#[test] #[test]
fn parse_quit() { fn parse_quit() {
let command = Command::parse(b"quit"); let command = parse_chkwhitespace!(b"quit");
assert_eq!(command, Ok(Command::Quit)); assert_eq!(command, Ok(Command::Quit));
} }
#[test] #[test]
fn parse_load() { fn parse_load() {
let command = Command::parse(b"load"); let command = parse_chkwhitespace!(b"load");
assert_eq!(command, Ok(Command::Load { channel: None })); assert_eq!(command, Ok(Command::Load { channel: None }));
} }
#[test] #[test]
fn parse_load_channel() { fn parse_load_channel() {
let command = Command::parse(b"load 0"); let command = parse_chkwhitespace!(b"load 0");
assert_eq!(command, Ok(Command::Load { channel: Some(0) })); assert_eq!(command, Ok(Command::Load { channel: Some(0) }));
} }
#[test] #[test]
fn parse_save() { fn parse_save() {
let command = Command::parse(b"save"); let command = parse_chkwhitespace!(b"save");
assert_eq!(command, Ok(Command::Save { channel: None })); assert_eq!(command, Ok(Command::Save { channel: None }));
} }
#[test] #[test]
fn parse_save_channel() { fn parse_save_channel() {
let command = Command::parse(b"save 0"); let command = parse_chkwhitespace!(b"save 0");
assert_eq!(command, Ok(Command::Save { channel: Some(0) })); assert_eq!(command, Ok(Command::Save { channel: Some(0) }));
} }
#[test] #[test]
fn parse_show_ipv4() { fn parse_show_ipv4() {
let command = Command::parse(b"ipv4"); let command = parse_chkwhitespace!(b"ipv4");
assert_eq!(command, Ok(Command::Show(ShowCommand::Ipv4))); assert_eq!(command, Ok(Command::Show(ShowCommand::Ipv4)));
} }
#[test] #[test]
fn parse_ipv4() { fn parse_ipv4() {
let command = Command::parse(b"ipv4 192.168.1.26/24"); let command = parse_chkwhitespace!(b"ipv4 192.168.1.26/24");
assert_eq!(command, Ok(Command::Ipv4(Ipv4Config { assert_eq!(command, Ok(Command::Ipv4(Ipv4Config {
address: [192, 168, 1, 26], address: [192, 168, 1, 26],
mask_len: 24, mask_len: 24,
@ -668,7 +678,7 @@ mod test {
#[test] #[test]
fn parse_ipv4_and_gateway() { fn parse_ipv4_and_gateway() {
let command = Command::parse(b"ipv4 10.42.0.126/8 10.1.0.1"); let command = parse_chkwhitespace!(b"ipv4 10.42.0.126/8 10.1.0.1");
assert_eq!(command, Ok(Command::Ipv4(Ipv4Config { assert_eq!(command, Ok(Command::Ipv4(Ipv4Config {
address: [10, 42, 0, 126], address: [10, 42, 0, 126],
mask_len: 8, mask_len: 8,
@ -678,31 +688,31 @@ mod test {
#[test] #[test]
fn parse_report() { fn parse_report() {
let command = Command::parse(b"report"); let command = parse_chkwhitespace!(b"report");
assert_eq!(command, Ok(Command::Show(ShowCommand::Input))); assert_eq!(command, Ok(Command::Show(ShowCommand::Input)));
} }
#[test] #[test]
fn parse_report_mode() { fn parse_report_mode() {
let command = Command::parse(b"report mode"); let command = parse_chkwhitespace!(b"report mode");
assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting))); assert_eq!(command, Ok(Command::Show(ShowCommand::Reporting)));
} }
#[test] #[test]
fn parse_report_mode_on() { fn parse_report_mode_on() {
let command = Command::parse(b"report mode on"); let command = parse_chkwhitespace!(b"report mode on");
assert_eq!(command, Ok(Command::Reporting(true))); assert_eq!(command, Ok(Command::Reporting(true)));
} }
#[test] #[test]
fn parse_report_mode_off() { fn parse_report_mode_off() {
let command = Command::parse(b"report mode off"); let command = parse_chkwhitespace!(b"report mode off");
assert_eq!(command, Ok(Command::Reporting(false))); assert_eq!(command, Ok(Command::Reporting(false)));
} }
#[test] #[test]
fn parse_pwm_i_set() { fn parse_pwm_i_set() {
let command = Command::parse(b"pwm 1 i_set 16383"); let command = parse_chkwhitespace!(b"pwm 1 i_set 16383");
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 1, channel: 1,
pin: PwmPin::ISet, pin: PwmPin::ISet,
@ -712,7 +722,7 @@ mod test {
#[test] #[test]
fn parse_pwm_pid() { fn parse_pwm_pid() {
let command = Command::parse(b"pwm 0 pid"); let command = parse_chkwhitespace!(b"pwm 0 pid");
assert_eq!(command, Ok(Command::PwmPid { assert_eq!(command, Ok(Command::PwmPid {
channel: 0, channel: 0,
})); }));
@ -720,7 +730,7 @@ mod test {
#[test] #[test]
fn parse_pwm_max_i_pos() { fn parse_pwm_max_i_pos() {
let command = Command::parse(b"pwm 0 max_i_pos 7"); let command = parse_chkwhitespace!(b"pwm 0 max_i_pos 7");
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxIPos, pin: PwmPin::MaxIPos,
@ -730,7 +740,7 @@ mod test {
#[test] #[test]
fn parse_pwm_max_i_neg() { fn parse_pwm_max_i_neg() {
let command = Command::parse(b"pwm 0 max_i_neg 128"); let command = parse_chkwhitespace!(b"pwm 0 max_i_neg 128");
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxINeg, pin: PwmPin::MaxINeg,
@ -740,7 +750,7 @@ mod test {
#[test] #[test]
fn parse_pwm_max_v() { fn parse_pwm_max_v() {
let command = Command::parse(b"pwm 0 max_v 32768"); let command = parse_chkwhitespace!(b"pwm 0 max_v 32768");
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxV, pin: PwmPin::MaxV,
@ -750,13 +760,13 @@ mod test {
#[test] #[test]
fn parse_pid() { fn parse_pid() {
let command = Command::parse(b"pid"); let command = parse_chkwhitespace!(b"pid");
assert_eq!(command, Ok(Command::Show(ShowCommand::Pid))); assert_eq!(command, Ok(Command::Show(ShowCommand::Pid)));
} }
#[test] #[test]
fn parse_pid_target() { fn parse_pid_target() {
let command = Command::parse(b"pid 0 target 36.5"); let command = parse_chkwhitespace!(b"pid 0 target 36.5");
assert_eq!(command, Ok(Command::Pid { assert_eq!(command, Ok(Command::Pid {
channel: 0, channel: 0,
parameter: PidParameter::Target, parameter: PidParameter::Target,
@ -766,13 +776,13 @@ mod test {
#[test] #[test]
fn parse_steinhart_hart() { fn parse_steinhart_hart() {
let command = Command::parse(b"s-h"); let command = parse_chkwhitespace!(b"s-h");
assert_eq!(command, Ok(Command::Show(ShowCommand::SteinhartHart))); assert_eq!(command, Ok(Command::Show(ShowCommand::SteinhartHart)));
} }
#[test] #[test]
fn parse_steinhart_hart_set() { fn parse_steinhart_hart_set() {
let command = Command::parse(b"s-h 1 t0 23.05"); let command = parse_chkwhitespace!(b"s-h 1 t0 23.05");
assert_eq!(command, Ok(Command::SteinhartHart { assert_eq!(command, Ok(Command::SteinhartHart {
channel: 1, channel: 1,
parameter: ShParameter::T0, parameter: ShParameter::T0,
@ -782,13 +792,13 @@ mod test {
#[test] #[test]
fn parse_postfilter() { fn parse_postfilter() {
let command = Command::parse(b"postfilter"); let command = parse_chkwhitespace!(b"postfilter");
assert_eq!(command, Ok(Command::Show(ShowCommand::PostFilter))); assert_eq!(command, Ok(Command::Show(ShowCommand::PostFilter)));
} }
#[test] #[test]
fn parse_postfilter_off() { fn parse_postfilter_off() {
let command = Command::parse(b"postfilter 1 off"); let command = parse_chkwhitespace!(b"postfilter 1 off");
assert_eq!(command, Ok(Command::PostFilter { assert_eq!(command, Ok(Command::PostFilter {
channel: 1, channel: 1,
rate: None, rate: None,
@ -797,7 +807,7 @@ mod test {
#[test] #[test]
fn parse_postfilter_rate() { fn parse_postfilter_rate() {
let command = Command::parse(b"postfilter 0 rate 21"); let command = parse_chkwhitespace!(b"postfilter 0 rate 21");
assert_eq!(command, Ok(Command::PostFilter { assert_eq!(command, Ok(Command::PostFilter {
channel: 0, channel: 0,
rate: Some(21.0), rate: Some(21.0),
@ -806,7 +816,7 @@ mod test {
#[test] #[test]
fn parse_center_point() { fn parse_center_point() {
let command = Command::parse(b"center 0 1.5"); let command = parse_chkwhitespace!(b"center 0 1.5");
assert_eq!(command, Ok(Command::CenterPoint { assert_eq!(command, Ok(Command::CenterPoint {
channel: 0, channel: 0,
center: CenterPoint::Override(1.5), center: CenterPoint::Override(1.5),
@ -815,7 +825,7 @@ mod test {
#[test] #[test]
fn parse_center_point_vref() { fn parse_center_point_vref() {
let command = Command::parse(b"center 1 vref"); let command = parse_chkwhitespace!(b"center 1 vref");
assert_eq!(command, Ok(Command::CenterPoint { assert_eq!(command, Ok(Command::CenterPoint {
channel: 1, channel: 1,
center: CenterPoint::Vref, center: CenterPoint::Vref,
@ -824,25 +834,25 @@ mod test {
#[test] #[test]
fn parse_fan_show() { fn parse_fan_show() {
let command = Command::parse(b"fan"); let command = parse_chkwhitespace!(b"fan");
assert_eq!(command, Ok(Command::ShowFan)); assert_eq!(command, Ok(Command::ShowFan));
} }
#[test] #[test]
fn parse_fan_set() { fn parse_fan_set() {
let command = Command::parse(b"fan 42"); let command = parse_chkwhitespace!(b"fan 42");
assert_eq!(command, Ok(Command::FanSet {fan_pwm: 42})); assert_eq!(command, Ok(Command::FanSet {fan_pwm: 42}));
} }
#[test] #[test]
fn parse_fan_auto() { fn parse_fan_auto() {
let command = Command::parse(b"fan auto"); let command = parse_chkwhitespace!(b"fan auto");
assert_eq!(command, Ok(Command::FanAuto)); assert_eq!(command, Ok(Command::FanAuto));
} }
#[test] #[test]
fn parse_fcurve_set() { fn parse_fcurve_set() {
let command = Command::parse(b"fcurve 1.2 3.4 5.6"); let command = parse_chkwhitespace!(b"fcurve 1.2 3.4 5.6");
assert_eq!(command, Ok(Command::FanCurve { assert_eq!(command, Ok(Command::FanCurve {
k_a: 1.2, k_a: 1.2,
k_b: 3.4, k_b: 3.4,
@ -852,13 +862,13 @@ mod test {
#[test] #[test]
fn parse_fcurve_default() { fn parse_fcurve_default() {
let command = Command::parse(b"fcurve default"); let command = parse_chkwhitespace!(b"fcurve default");
assert_eq!(command, Ok(Command::FanCurveDefaults)); assert_eq!(command, Ok(Command::FanCurveDefaults));
} }
#[test] #[test]
fn parse_hwrev() { fn parse_hwrev() {
let command = Command::parse(b"hwrev"); let command = parse_chkwhitespace!(b"hwrev");
assert_eq!(command, Ok(Command::ShowHWRev)); assert_eq!(command, Ok(Command::ShowHWRev));
} }
} }

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,13 +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() {
leds.g3.on();
} else {
leds.g3.off();
}
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);
@ -216,7 +211,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,