Compare commits
9 Commits
cee0a1fcab
...
bb51cce6f6
Author | SHA1 | Date |
---|---|---|
Astro | bb51cce6f6 | |
Astro | 723901b341 | |
Astro | 2e72a03b93 | |
Astro | bec7019f3a | |
Astro | c00c1bb081 | |
Astro | a94650e208 | |
Astro | 163bb38f68 | |
Astro | e5b4789304 | |
Astro | da4aaf4ff6 |
|
@ -20,6 +20,12 @@ dependencies = [
|
|||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bare-metal"
|
||||
version = "0.2.5"
|
||||
|
@ -149,14 +155,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "857afb5ee9e767c3a73b2ad7212b6deea0c3761a27db1e20ea0ed57ee352cfef"
|
||||
|
||||
[[package]]
|
||||
name = "lexical-core"
|
||||
version = "0.7.4"
|
||||
name = "libm"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
]
|
||||
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -195,6 +197,16 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "panic-abort"
|
||||
version = "0.3.2"
|
||||
|
@ -235,6 +247,12 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2a38df5b15c8d5c7e8654189744d8e396bddc18ad48041a500ce52d6948941f"
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
|
@ -290,9 +308,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "stm32f4"
|
||||
version = "0.9.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "88640ad08c62e0651a1320187f38c3655d025ed580a10f0e4d85a2cc4829069f"
|
||||
checksum = "44a3d6c58b14e63926273694e7dd644894513c5e35ce6928c4657ddb62cae976"
|
||||
dependencies = [
|
||||
"bare-metal",
|
||||
"cortex-m",
|
||||
|
@ -302,8 +320,8 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "stm32f4xx-hal"
|
||||
version = "0.6.0"
|
||||
source = "git+https://github.com/thalesfragoso/stm32f4xx-hal?branch=pwm-impl#ef939935b90581553dc03f9146d05510b3ceba58"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/thalesfragoso/stm32f4xx-hal?branch=pwm-impl#cfd073e094daa9be9dd2b0a1f859a4e1c6be2b77"
|
||||
dependencies = [
|
||||
"bare-metal",
|
||||
"cast",
|
||||
|
@ -311,6 +329,7 @@ dependencies = [
|
|||
"cortex-m-rt",
|
||||
"embedded-hal",
|
||||
"nb",
|
||||
"rand_core",
|
||||
"stm32f4",
|
||||
"void",
|
||||
]
|
||||
|
@ -338,9 +357,9 @@ dependencies = [
|
|||
"cortex-m-rt",
|
||||
"embedded-hal",
|
||||
"hash2hwaddr",
|
||||
"lexical-core",
|
||||
"log",
|
||||
"nom",
|
||||
"num-traits",
|
||||
"panic-abort",
|
||||
"panic-semihosting",
|
||||
"smoltcp",
|
||||
|
|
|
@ -30,7 +30,7 @@ hash2hwaddr = { version = "0.0", optional = true }
|
|||
bit_field = "0.10"
|
||||
byteorder = { version = "1", default-features = false }
|
||||
nom = { version = "5", default-features = false }
|
||||
lexical-core = { version = "0.7", default-features = false }
|
||||
num-traits = { version = "0.2", default-features = false, features = ["libm"] }
|
||||
|
||||
[features]
|
||||
semihosting = ["panic-semihosting", "cortex-m-log/semihosting"]
|
||||
|
|
|
@ -14,6 +14,8 @@ pub const SPI_MODE: spi::Mode = spi::Mode {
|
|||
/// 30 MHz
|
||||
pub const SPI_CLOCK: MegaHertz = MegaHertz(30);
|
||||
|
||||
pub const MAX_VALUE: u32 = 0x20000;
|
||||
|
||||
/// [AD5680](https://www.analog.com/media/en/technical-documentation/data-sheets/AD5680.pdf) DAC
|
||||
pub struct Dac<SPI: Transfer<u8>, S: OutputPin> {
|
||||
spi: SPI,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::fmt;
|
||||
use embedded_hal::digital::v2::OutputPin;
|
||||
use embedded_hal::blocking::spi::Transfer;
|
||||
use log::{info, warn};
|
||||
use log::info;
|
||||
use super::checksum::{ChecksumMode, Checksum};
|
||||
use super::AdcError;
|
||||
use super::{
|
||||
|
@ -129,7 +129,7 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||
}
|
||||
|
||||
/// Get data
|
||||
pub fn read_data(&mut self) -> Result<i32, AdcError<SPI::Error>> {
|
||||
pub fn read_data(&mut self) -> Result<u32, AdcError<SPI::Error>> {
|
||||
self.read_reg(®s::Data)
|
||||
.map(|data| data.data())
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use core::fmt;
|
||||
use lexical_core::Float;
|
||||
use num_traits::float::Float;
|
||||
use stm32f4xx_hal::{
|
||||
time::{MegaHertz, U32Ext},
|
||||
time::MegaHertz,
|
||||
spi,
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,8 @@ pub const SPI_MODE: spi::Mode = spi::Mode {
|
|||
/// 2 MHz
|
||||
pub const SPI_CLOCK: MegaHertz = MegaHertz(2);
|
||||
|
||||
pub const MAX_VALUE: u32 = 0xFF_FFFF;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum AdcError<SPI> {
|
||||
SPI(SPI),
|
||||
|
|
|
@ -160,16 +160,10 @@ impl if_mode::Data {
|
|||
|
||||
def_reg!(Data, data, 0x04, 3);
|
||||
impl data::Data {
|
||||
pub fn data(&self) -> i32 {
|
||||
let raw =
|
||||
(u32::from(self.0[0]) << 16) |
|
||||
(u32::from(self.0[1]) << 8) |
|
||||
u32::from(self.0[2]);
|
||||
if raw & 0x80_0000 != 0 {
|
||||
((raw & 0x7F_FFFF) | 0x8000_0000) as i32
|
||||
} else {
|
||||
raw as i32
|
||||
}
|
||||
pub fn data(&self) -> u32 {
|
||||
(u32::from(self.0[0]) << 16) |
|
||||
(u32::from(self.0[1]) << 8) |
|
||||
u32::from(self.0[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,21 +199,6 @@ impl channel::Data {
|
|||
}
|
||||
reg_bits!(a_in_neg, set_a_in_neg, 1, 0..=4, Input,
|
||||
"Which input is connected to negative input of this channel");
|
||||
|
||||
// const PROPS: &'static [Property<Self>] = &[
|
||||
// Property::named("enable")
|
||||
// .readable(&|self_: &Self| self_.enabled().into())
|
||||
// .writebale(&|self_: &mut Self, value| self_.set_enabled(value != 0)),
|
||||
// Property::named("setup")
|
||||
// .readable(&|self_: &Self| self_.0[0].get_bits(4..=5).into())
|
||||
// .writeable(&|self_: &mut Self, value| {
|
||||
// self_.0[0].set_bits(4..=5, value as u8);
|
||||
// }),
|
||||
// ];
|
||||
|
||||
// pub fn props() -> &'static [Property<Self>] {
|
||||
// Self::PROPS
|
||||
// }
|
||||
}
|
||||
|
||||
def_reg!(SetupCon, u8, setup_con, 0x20, 2);
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
use smoltcp::time::Instant;
|
||||
use crate::{ad5680, ad7172, pid, steinhart_hart as sh};
|
||||
|
||||
|
||||
pub struct ChannelState {
|
||||
pub adc_data: Option<u32>,
|
||||
pub adc_time: Instant,
|
||||
pub dac_value: u32,
|
||||
pub pid_enabled: bool,
|
||||
pub pid: pid::Controller,
|
||||
pub sh: sh::Parameters,
|
||||
}
|
||||
|
||||
impl Default for ChannelState {
|
||||
fn default() -> Self {
|
||||
ChannelState {
|
||||
adc_data: None,
|
||||
adc_time: Instant::from_secs(0),
|
||||
dac_value: 0,
|
||||
pid_enabled: false,
|
||||
pid: pid::Controller::new(pid::Parameters::default()),
|
||||
sh: sh::Parameters::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ChannelState {
|
||||
/// Update PID state on ADC input, calculate new DAC output
|
||||
pub fn update_adc(&mut self, now: Instant, adc_data: u32) {
|
||||
self.adc_data = Some(adc_data);
|
||||
self.adc_time = now;
|
||||
|
||||
// Update PID controller
|
||||
let input = (adc_data as f64) / (ad7172::MAX_VALUE as f64);
|
||||
let temperature = self.sh.get_temperature(input);
|
||||
let output = self.pid.update(temperature);
|
||||
self.dac_value = (output * (ad5680::MAX_VALUE as f64)) as u32;
|
||||
}
|
||||
}
|
|
@ -1,15 +1,17 @@
|
|||
use core::fmt;
|
||||
use core::num::ParseIntError;
|
||||
use core::str::{from_utf8, Utf8Error};
|
||||
use nom::{
|
||||
IResult,
|
||||
branch::alt,
|
||||
bytes::complete::{is_a, tag, take_while1},
|
||||
character::{is_digit, complete::{char, one_of}},
|
||||
combinator::{complete, map, opt, value},
|
||||
sequence::{preceded, separated_pair},
|
||||
sequence::preceded,
|
||||
multi::{fold_many0, fold_many1},
|
||||
error::ErrorKind,
|
||||
};
|
||||
use lexical_core as lexical;
|
||||
use num_traits::{Num, ParseFloatError};
|
||||
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
|
@ -17,7 +19,10 @@ pub enum Error {
|
|||
Parser(ErrorKind),
|
||||
Incomplete,
|
||||
UnexpectedInput(u8),
|
||||
ParseNumber(lexical::Error)
|
||||
Utf8(Utf8Error),
|
||||
ParseInt(ParseIntError),
|
||||
// `num_traits::ParseFloatError` does not impl Clone
|
||||
ParseFloat,
|
||||
}
|
||||
|
||||
impl<'t> From<nom::Err<(&'t [u8], ErrorKind)>> for Error {
|
||||
|
@ -33,9 +38,21 @@ impl<'t> From<nom::Err<(&'t [u8], ErrorKind)>> for Error {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<lexical::Error> for Error {
|
||||
fn from(e: lexical::Error) -> Self {
|
||||
Error::ParseNumber(e)
|
||||
impl From<Utf8Error> for Error {
|
||||
fn from(e: Utf8Error) -> Self {
|
||||
Error::Utf8(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseIntError> for Error {
|
||||
fn from(e: ParseIntError) -> Self {
|
||||
Error::ParseInt(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ParseFloatError> for Error {
|
||||
fn from(_: ParseFloatError) -> Self {
|
||||
Error::ParseFloat
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -52,10 +69,17 @@ impl fmt::Display for Error {
|
|||
"parser: ".fmt(fmt)?;
|
||||
(e as &dyn core::fmt::Debug).fmt(fmt)
|
||||
}
|
||||
Error::ParseNumber(e) => {
|
||||
"parsing number: ".fmt(fmt)?;
|
||||
Error::Utf8(e) => {
|
||||
"utf8: ".fmt(fmt)?;
|
||||
(e as &dyn core::fmt::Debug).fmt(fmt)
|
||||
}
|
||||
Error::ParseInt(e) => {
|
||||
"parsing int: ".fmt(fmt)?;
|
||||
(e as &dyn core::fmt::Debug).fmt(fmt)
|
||||
}
|
||||
Error::ParseFloat => {
|
||||
"parsing float".fmt(fmt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -85,30 +109,28 @@ pub enum PidParameter {
|
|||
/// Steinhart-Hart equation parameter
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum ShParameter {
|
||||
A,
|
||||
T0,
|
||||
B,
|
||||
C,
|
||||
ParallelR,
|
||||
R0,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PwmConfig {
|
||||
pub width: u16,
|
||||
pub total: u16,
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum PwmPin {
|
||||
ISet,
|
||||
MaxIPos,
|
||||
MaxINeg,
|
||||
MaxV,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PwmMode {
|
||||
Manual(PwmConfig),
|
||||
Pid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PwmSetup {
|
||||
ISet(PwmMode),
|
||||
MaxIPos(PwmConfig),
|
||||
MaxINeg(PwmConfig),
|
||||
MaxV(PwmConfig),
|
||||
impl PwmPin {
|
||||
pub fn name(&self) -> &'static str {
|
||||
match self {
|
||||
PwmPin::ISet => "i_set",
|
||||
PwmPin::MaxIPos => "max_i_pos",
|
||||
PwmPin::MaxINeg => "max_i_neg",
|
||||
PwmPin::MaxV => "max_v",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
|
@ -116,10 +138,17 @@ pub enum Command {
|
|||
Quit,
|
||||
Show(ShowCommand),
|
||||
Reporting(bool),
|
||||
/// PWM parameter setting
|
||||
Pwm {
|
||||
channel: usize,
|
||||
setup: PwmSetup,
|
||||
pin: PwmPin,
|
||||
duty: u32,
|
||||
},
|
||||
/// Enable PID control for `i_set`
|
||||
PwmPid {
|
||||
channel: usize,
|
||||
},
|
||||
/// PID parameter setting
|
||||
Pid {
|
||||
channel: usize,
|
||||
parameter: PidParameter,
|
||||
|
@ -149,11 +178,15 @@ fn whitespace(input: &[u8]) -> IResult<&[u8], ()> {
|
|||
fold_many1(char(' '), (), |(), _| ())(input)
|
||||
}
|
||||
|
||||
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u16, Error>> {
|
||||
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u32, Error>> {
|
||||
take_while1(is_digit)(input)
|
||||
.map(|(input, digits)| {
|
||||
let result = lexical::parse(digits)
|
||||
.map_err(|e| e.into());
|
||||
let result =
|
||||
from_utf8(digits)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|digits| u32::from_str_radix(digits, 10)
|
||||
.map_err(|e| e.into())
|
||||
);
|
||||
(input, result)
|
||||
})
|
||||
}
|
||||
|
@ -162,9 +195,13 @@ fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
|
|||
let (input, sign) = opt(is_a("-"))(input)?;
|
||||
let negative = sign.is_some();
|
||||
let (input, digits) = take_while1(|c| is_digit(c) || c == '.' as u8)(input)?;
|
||||
let result = lexical::parse(digits)
|
||||
.map(|result: f64| if negative { -result } else { result })
|
||||
.map_err(|e| e.into());
|
||||
let result =
|
||||
from_utf8(digits)
|
||||
.map_err(|e| e.into())
|
||||
.and_then(|digits| f64::from_str_radix(digits, 10)
|
||||
.map_err(|e| e.into())
|
||||
)
|
||||
.map(|result: f64| if negative { -result } else { result });
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
|
@ -202,82 +239,77 @@ fn report(input: &[u8]) -> IResult<&[u8], Command> {
|
|||
)(input)
|
||||
}
|
||||
|
||||
/// `pwm ... <width> <total>` - Set pwm duty cycle
|
||||
fn pwm_config(input: &[u8]) -> IResult<&[u8], Result<PwmConfig, Error>> {
|
||||
let (input, width) = unsigned(input)?;
|
||||
let width = match width {
|
||||
Ok(width) => width,
|
||||
Err(e) => return Ok((input, Err(e.into()))),
|
||||
};
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, total) = unsigned(input)?;
|
||||
let total = match total {
|
||||
Ok(total) => total,
|
||||
Err(e) => return Ok((input, Err(e.into()))),
|
||||
};
|
||||
Ok((input, Ok(PwmConfig { width, total })))
|
||||
}
|
||||
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<(PwmPin, u32), Error>> {
|
||||
let result_with_pin = |pin: PwmPin|
|
||||
move |result: Result<u32, Error>|
|
||||
result.map(|duty| (pin, duty));
|
||||
|
||||
fn pwm_setup(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
|
||||
alt((
|
||||
map(
|
||||
preceded(
|
||||
tag("max_i_pos"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
unsigned
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxIPos)
|
||||
result_with_pin(PwmPin::MaxIPos)
|
||||
),
|
||||
map(
|
||||
preceded(
|
||||
tag("max_i_neg"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
unsigned
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxINeg)
|
||||
result_with_pin(PwmPin::MaxINeg)
|
||||
),
|
||||
map(
|
||||
preceded(
|
||||
tag("max_v"),
|
||||
preceded(
|
||||
whitespace,
|
||||
pwm_config
|
||||
unsigned
|
||||
)
|
||||
),
|
||||
|result| result.map(PwmSetup::MaxV)
|
||||
result_with_pin(PwmPin::MaxV)
|
||||
),
|
||||
map(pwm_config, |result| result.map(|config| {
|
||||
PwmSetup::ISet(PwmMode::Manual(config))
|
||||
}))
|
||||
))(input)
|
||||
map(unsigned, result_with_pin(PwmPin::ISet)
|
||||
))
|
||||
)(input)
|
||||
}
|
||||
|
||||
/// `pwm <0-1> pid` - Set PWM to be controlled by PID
|
||||
fn pwm_pid(input: &[u8]) -> IResult<&[u8], Result<PwmSetup, Error>> {
|
||||
value(Ok(PwmSetup::ISet(PwmMode::Pid)), tag("pid"))(input)
|
||||
fn pwm_pid(input: &[u8]) -> IResult<&[u8], ()> {
|
||||
value((), tag("pid"))(input)
|
||||
}
|
||||
|
||||
fn pwm(input: &[u8]) -> IResult<&[u8], Result<Command, Error>> {
|
||||
let (input, _) = tag("pwm")(input)?;
|
||||
alt((
|
||||
preceded(
|
||||
whitespace,
|
||||
map(
|
||||
separated_pair(
|
||||
channel,
|
||||
whitespace,
|
||||
alt((
|
||||
pwm_pid,
|
||||
pwm_setup
|
||||
))
|
||||
),
|
||||
|(channel, setup)| setup.map(|setup| Command::Pwm { channel, setup })
|
||||
)
|
||||
),
|
||||
|input| {
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, channel) = channel(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, result) = alt((
|
||||
|input| {
|
||||
let (input, ()) = pwm_pid(input)?;
|
||||
Ok((input, Ok(Command::PwmPid { channel })))
|
||||
},
|
||||
|input| {
|
||||
let (input, config) = pwm_setup(input)?;
|
||||
match config {
|
||||
Ok((pin, duty)) =>
|
||||
Ok((input, Ok(Command::Pwm { channel, pin, duty }))),
|
||||
Err(e) =>
|
||||
Ok((input, Err(e))),
|
||||
}
|
||||
},
|
||||
))(input)?;
|
||||
end(input)?;
|
||||
Ok((input, result))
|
||||
},
|
||||
value(Ok(Command::Show(ShowCommand::Pwm)), end)
|
||||
))(input)
|
||||
}
|
||||
|
@ -320,10 +352,9 @@ fn steinhart_hart_parameter(input: &[u8]) -> IResult<&[u8], Result<Command, Erro
|
|||
let (input, channel) = channel(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, parameter) =
|
||||
alt((value(ShParameter::A, tag("a")),
|
||||
alt((value(ShParameter::T0, tag("t0")),
|
||||
value(ShParameter::B, tag("b")),
|
||||
value(ShParameter::C, tag("c")),
|
||||
value(ShParameter::ParallelR, tag("parallel_r"))
|
||||
value(ShParameter::R0, tag("r0"))
|
||||
))(input)?;
|
||||
let (input, _) = whitespace(input)?;
|
||||
let (input, value) = float(input)?;
|
||||
|
@ -426,58 +457,50 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn parse_pwm_manual() {
|
||||
let command = Command::parse(b"pwm 1 16383 65535");
|
||||
let command = Command::parse(b"pwm 1 16383");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 1,
|
||||
setup: PwmSetup::ISet(PwmMode::Manual(PwmConfig {
|
||||
width: 16383,
|
||||
total: 65535,
|
||||
})),
|
||||
pin: PwmPin::ISet,
|
||||
duty: 16383,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_pid() {
|
||||
let command = Command::parse(b"pwm 0 pid");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
assert_eq!(command, Ok(Command::PwmPid {
|
||||
channel: 0,
|
||||
setup: PwmSetup::ISet(PwmMode::Pid),
|
||||
pin: PwmPin::ISet,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_i_pos() {
|
||||
let command = Command::parse(b"pwm 0 max_i_pos 7 13");
|
||||
let command = Command::parse(b"pwm 0 max_i_pos 7");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxIPos(PwmConfig {
|
||||
width: 7,
|
||||
total: 13,
|
||||
}),
|
||||
pin: PwmPin::MaxIPos,
|
||||
duty: 7,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_i_neg() {
|
||||
let command = Command::parse(b"pwm 0 max_i_neg 128 65535");
|
||||
let command = Command::parse(b"pwm 0 max_i_neg 128");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxINeg(PwmConfig {
|
||||
width: 128,
|
||||
total: 65535,
|
||||
}),
|
||||
pin: PwmPin::MaxINeg,
|
||||
duty: 128,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_pwm_max_v() {
|
||||
let command = Command::parse(b"pwm 0 max_v 32768 65535");
|
||||
let command = Command::parse(b"pwm 0 max_v 32768");
|
||||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
setup: PwmSetup::MaxV(PwmConfig {
|
||||
width: 32768,
|
||||
total: 65535,
|
||||
}),
|
||||
pin: PwmPin::MaxV,
|
||||
duty: 32768,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -515,10 +538,10 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn parse_steinhart_hart_parallel_r() {
|
||||
let command = Command::parse(b"s-h 1 parallel_r 23.05");
|
||||
let command = Command::parse(b"s-h 1 t0 23.05");
|
||||
assert_eq!(command, Ok(Command::SteinhartHart {
|
||||
channel: 1,
|
||||
parameter: ShParameter::ParallelR,
|
||||
parameter: ShParameter::T0,
|
||||
value: 23.05,
|
||||
}));
|
||||
}
|
||||
|
|
390
src/main.rs
390
src/main.rs
|
@ -9,11 +9,15 @@ use panic_semihosting as _;
|
|||
|
||||
use log::{info, warn};
|
||||
|
||||
use core::ops::DerefMut;
|
||||
use core::fmt::Write;
|
||||
use cortex_m::asm::wfi;
|
||||
use cortex_m_rt::entry;
|
||||
use embedded_hal::watchdog::{WatchdogEnable, Watchdog};
|
||||
use stm32f4xx_hal::{
|
||||
hal::{
|
||||
self,
|
||||
watchdog::{WatchdogEnable, Watchdog},
|
||||
},
|
||||
rcc::RccExt,
|
||||
watchdog::IndependentWatchdog,
|
||||
time::{U32Ext, MegaHertz},
|
||||
|
@ -36,28 +40,15 @@ use server::Server;
|
|||
mod session;
|
||||
use session::{CHANNELS, Session, SessionOutput};
|
||||
mod command_parser;
|
||||
use command_parser::{Command, ShowCommand, PwmSetup, PwmMode};
|
||||
use command_parser::{Command, ShowCommand, PwmPin};
|
||||
mod timer;
|
||||
mod pid;
|
||||
mod steinhart_hart;
|
||||
mod channel_state;
|
||||
use channel_state::ChannelState;
|
||||
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct ChannelState {
|
||||
adc_data: Option<i32>,
|
||||
adc_time: Instant,
|
||||
}
|
||||
|
||||
impl Default for ChannelState {
|
||||
fn default() -> Self {
|
||||
ChannelState {
|
||||
adc_data: None,
|
||||
adc_time: Instant::from_secs(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const HSE: MegaHertz = MegaHertz(8);
|
||||
#[cfg(not(feature = "semihosting"))]
|
||||
const WATCHDOG_INTERVAL: u32 = 100;
|
||||
#[cfg(feature = "semihosting")]
|
||||
|
@ -65,12 +56,9 @@ const WATCHDOG_INTERVAL: u32 = 10_000;
|
|||
|
||||
#[cfg(not(feature = "generate-hwaddr"))]
|
||||
const NET_HWADDR: [u8; 6] = [0x02, 0x00, 0xDE, 0xAD, 0xBE, 0xEF];
|
||||
|
||||
const TCP_PORT: u16 = 23;
|
||||
|
||||
|
||||
const HSE: MegaHertz = MegaHertz(8);
|
||||
|
||||
/// Initialization and main loop
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
|
@ -105,9 +93,10 @@ fn main() -> ! {
|
|||
|
||||
let mut adc = ad7172::Adc::new(pins.adc_spi, pins.adc_nss).unwrap();
|
||||
let mut dac0 = ad5680::Dac::new(pins.dac0_spi, pins.dac0_sync);
|
||||
dac0.set(0);
|
||||
dac0.set(0).unwrap();
|
||||
let mut dac1 = ad5680::Dac::new(pins.dac1_spi, pins.dac1_sync);
|
||||
dac1.set(0);
|
||||
dac1.set(0).unwrap();
|
||||
let mut pwm = pins.pwm;
|
||||
|
||||
timer::setup(cp.SYST, clocks);
|
||||
|
||||
|
@ -120,7 +109,9 @@ fn main() -> ! {
|
|||
};
|
||||
info!("Net hwaddr: {}", hwaddr);
|
||||
|
||||
let mut channel_states = [ChannelState::default(); CHANNELS];
|
||||
let mut channel_states: [ChannelState; CHANNELS] = [
|
||||
ChannelState::default(), ChannelState::default()
|
||||
];
|
||||
|
||||
net::run(dp.ETHERNET_MAC, dp.ETHERNET_DMA, hwaddr, |iface| {
|
||||
Server::<Session>::run(iface, |server| {
|
||||
|
@ -138,8 +129,20 @@ fn main() -> ! {
|
|||
let data = adc.read_data().unwrap();
|
||||
|
||||
let state = &mut channel_states[usize::from(channel)];
|
||||
state.adc_data = Some(data);
|
||||
state.adc_time = instant;
|
||||
state.update_adc(instant, data);
|
||||
|
||||
if state.pid_enabled {
|
||||
// Forward PID output to i_set DAC
|
||||
match channel {
|
||||
0 =>
|
||||
dac0.set(state.dac_value).unwrap(),
|
||||
1 =>
|
||||
dac1.set(state.dac_value).unwrap(),
|
||||
_ =>
|
||||
unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
server.for_each(|_, session| session.set_report_pending(channel.into()));
|
||||
});
|
||||
|
||||
|
@ -147,7 +150,9 @@ fn main() -> ! {
|
|||
server.for_each(|mut socket, session| {
|
||||
if ! socket.is_open() {
|
||||
let _ = socket.listen(TCP_PORT);
|
||||
session.reset();
|
||||
if session.is_dirty() {
|
||||
session.reset();
|
||||
}
|
||||
} else if socket.can_send() && socket.can_recv() && socket.send_capacity() - socket.send_queue() > 128 {
|
||||
match socket.recv(|buf| session.feed(buf)) {
|
||||
Ok(SessionOutput::Nothing) => {}
|
||||
|
@ -167,170 +172,211 @@ fn main() -> ! {
|
|||
socket, "t={} raw{}=0x{:06X}",
|
||||
state.adc_time, channel, adc_data
|
||||
);
|
||||
// TODO: show pwm status et al
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::Pid) => {
|
||||
// for (channel, state) in states.iter().enumerate() {
|
||||
// let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||
// let pid = &state.pid;
|
||||
// let _ = writeln!(socket, "- target={:.4}", pid.get_target());
|
||||
// let p = pid.get_parameters();
|
||||
// macro_rules! out {
|
||||
// ($p: tt) => {
|
||||
// let _ = writeln!(socket, "- {}={:.4}", stringify!($p), p.$p);
|
||||
// };
|
||||
// }
|
||||
// out!(kp);
|
||||
// out!(ki);
|
||||
// out!(kd);
|
||||
// out!(output_min);
|
||||
// out!(output_max);
|
||||
// out!(integral_min);
|
||||
// out!(integral_max);
|
||||
// let _ = writeln!(socket, "");
|
||||
// }
|
||||
for (channel, state) in channel_states.iter().enumerate() {
|
||||
let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||
let pid = &state.pid;
|
||||
let _ = writeln!(socket, "- target={:.4}", pid.target);
|
||||
macro_rules! show_pid_parameter {
|
||||
($p: tt) => {
|
||||
let _ = writeln!(
|
||||
socket, "- {}={:.4}",
|
||||
stringify!($p), pid.parameters.$p
|
||||
);
|
||||
};
|
||||
}
|
||||
show_pid_parameter!(kp);
|
||||
show_pid_parameter!(ki);
|
||||
show_pid_parameter!(kd);
|
||||
show_pid_parameter!(output_min);
|
||||
show_pid_parameter!(output_max);
|
||||
show_pid_parameter!(integral_min);
|
||||
show_pid_parameter!(integral_max);
|
||||
if let Some(last_output) = pid.last_output {
|
||||
let _ = writeln!(socket, "- output={:.4}", last_output);
|
||||
}
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::Pwm) => {
|
||||
// for (channel, state) in states.iter().enumerate() {
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: PID={}",
|
||||
// channel,
|
||||
// if state.pid_enabled { "engaged" } else { "disengaged" }
|
||||
// );
|
||||
// for pin in TecPin::VALID_VALUES {
|
||||
// let (width, total) = match channel {
|
||||
// 0 => tec0.get(*pin),
|
||||
// 1 => tec1.get(*pin),
|
||||
// _ => unreachable!(),
|
||||
// };
|
||||
// let _ = writeln!(socket, "- {}={}/{}", pin, width, total);
|
||||
// }
|
||||
// let _ = writeln!(socket, "");
|
||||
// }
|
||||
for (channel, state) in channel_states.iter().enumerate() {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PID={}",
|
||||
channel,
|
||||
if state.pid_enabled { "engaged" } else { "disengaged" }
|
||||
);
|
||||
let _ = writeln!(socket, "- i_set={}/{}", state.dac_value, ad5680::MAX_VALUE);
|
||||
fn show_pwm_channel<S, P>(mut socket: S, name: &str, pin: &P)
|
||||
where
|
||||
S: core::fmt::Write,
|
||||
P: hal::PwmPin<Duty=u16>,
|
||||
{
|
||||
let _ = writeln!(
|
||||
socket,
|
||||
"- {}={}/{}",
|
||||
name, pin.get_duty(), pin.get_max_duty()
|
||||
);
|
||||
}
|
||||
match channel {
|
||||
0 => {
|
||||
show_pwm_channel(socket.deref_mut(), "max_v", &pwm.max_v0);
|
||||
show_pwm_channel(socket.deref_mut(), "max_i_pos", &pwm.max_i_pos0);
|
||||
show_pwm_channel(socket.deref_mut(), "max_i_neg", &pwm.max_i_neg0);
|
||||
}
|
||||
1 => {
|
||||
show_pwm_channel(socket.deref_mut(), "max_v", &pwm.max_v1);
|
||||
show_pwm_channel(socket.deref_mut(), "max_i_pos", &pwm.max_i_pos1);
|
||||
show_pwm_channel(socket.deref_mut(), "max_i_neg", &pwm.max_i_neg1);
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::SteinhartHart) => {
|
||||
// for (channel, state) in states.iter().enumerate() {
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: Steinhart-Hart equation parameters",
|
||||
// channel,
|
||||
// );
|
||||
// let _ = writeln!(socket, "- a={}", state.sh.a);
|
||||
// let _ = writeln!(socket, "- b={}", state.sh.b);
|
||||
// let _ = writeln!(socket, "- c={}", state.sh.c);
|
||||
// let _ = writeln!(socket, "- parallel_r={}", state.sh.parallel_r);
|
||||
// let _ = writeln!(socket, "");
|
||||
// }
|
||||
for (channel, state) in channel_states.iter().enumerate() {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: Steinhart-Hart equation parameters",
|
||||
channel,
|
||||
);
|
||||
let _ = writeln!(socket, "- t0={}", state.sh.t0);
|
||||
let _ = writeln!(socket, "- b={}", state.sh.b);
|
||||
let _ = writeln!(socket, "- r0={}", state.sh.r0);
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
}
|
||||
Command::Show(ShowCommand::PostFilter) => {
|
||||
// for (channel, _) in states.iter().enumerate() {
|
||||
// match adc.get_postfilter(channel as u8).unwrap() {
|
||||
// Some(filter) => {
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: postfilter={:.2} SPS",
|
||||
// channel, filter.output_rate().unwrap()
|
||||
// );
|
||||
// }
|
||||
// None => {
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: no postfilter",
|
||||
// channel
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
for (channel, _) in channel_states.iter().enumerate() {
|
||||
match adc.get_postfilter(channel as u8).unwrap() {
|
||||
Some(filter) => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter={:.2} SPS",
|
||||
channel, filter.output_rate().unwrap()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: no postfilter",
|
||||
channel
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Pid) } => {
|
||||
// states[channel].pid_enabled = true;
|
||||
// let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel
|
||||
// );
|
||||
Command::PwmPid { channel } => {
|
||||
channel_states[channel].pid_enabled = true;
|
||||
let _ = writeln!(socket, "channel {}: PID enabled to control PWM", channel
|
||||
);
|
||||
}
|
||||
Command::Pwm { channel, setup: PwmSetup::ISet(PwmMode::Manual(config))} => {
|
||||
// states[channel].pid_enabled = false;
|
||||
// let PwmConfig { width, total } = config;
|
||||
// match channel {
|
||||
// 0 => tec0.set(TecPin::ISet, width, total),
|
||||
// 1 => tec1.set(TecPin::ISet, width, total),
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: PWM duty cycle manually set to {}/{}",
|
||||
// channel, config.width, config.total
|
||||
// );
|
||||
Command::Pwm { channel, pin: PwmPin::ISet, duty } if duty <= ad5680::MAX_VALUE => {
|
||||
channel_states[channel].pid_enabled = false;
|
||||
match channel {
|
||||
0 => dac0.set(duty).unwrap(),
|
||||
1 => dac1.set(duty).unwrap(),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
channel_states[channel].dac_value = duty;
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PWM duty cycle manually set to {}/{}",
|
||||
channel, duty, ad5680::MAX_VALUE
|
||||
);
|
||||
}
|
||||
Command::Pwm { channel, setup } => {
|
||||
// let (pin, config) = match setup {
|
||||
// PwmSetup::ISet(_) =>
|
||||
// // Handled above
|
||||
// unreachable!(),
|
||||
// PwmSetup::MaxIPos(config) =>
|
||||
// (TecPin::MaxIPos, config),
|
||||
// PwmSetup::MaxINeg(config) =>
|
||||
// (TecPin::MaxINeg, config),
|
||||
// PwmSetup::MaxV(config) =>
|
||||
// (TecPin::MaxV, config),
|
||||
// };
|
||||
// let PwmConfig { width, total } = config;
|
||||
// match channel {
|
||||
// 0 => tec0.set(pin, width, total),
|
||||
// 1 => tec1.set(pin, width, total),
|
||||
// _ => unreachable!(),
|
||||
// }
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: PWM {} reconfigured to {}/{}",
|
||||
// channel, pin, width, total
|
||||
// );
|
||||
Command::Pwm { pin: PwmPin::ISet, duty, .. } if duty > ad5680::MAX_VALUE => {
|
||||
let _ = writeln!(
|
||||
socket, "error: PWM duty range must not exceed {}",
|
||||
ad5680::MAX_VALUE
|
||||
);
|
||||
}
|
||||
Command::Pwm { channel, pin, duty } if duty <= 0xFFFF => {
|
||||
let duty = duty as u16;
|
||||
|
||||
fn set_pwm_channel<P: hal::PwmPin<Duty=u16>>(pin: &mut P, duty: u16) -> u16 {
|
||||
pin.set_duty(duty);
|
||||
pin.get_max_duty()
|
||||
}
|
||||
let max = match (channel, pin) {
|
||||
(_, PwmPin::ISet) =>
|
||||
// Handled above
|
||||
unreachable!(),
|
||||
(0, PwmPin::MaxIPos) =>
|
||||
set_pwm_channel(&mut pwm.max_i_pos0, duty),
|
||||
(0, PwmPin::MaxINeg) =>
|
||||
set_pwm_channel(&mut pwm.max_i_neg0, duty),
|
||||
(0, PwmPin::MaxV) =>
|
||||
set_pwm_channel(&mut pwm.max_v0, duty),
|
||||
(1, PwmPin::MaxIPos) =>
|
||||
set_pwm_channel(&mut pwm.max_i_pos1, duty),
|
||||
(1, PwmPin::MaxINeg) =>
|
||||
set_pwm_channel(&mut pwm.max_i_neg1, duty),
|
||||
(1, PwmPin::MaxV) =>
|
||||
set_pwm_channel(&mut pwm.max_v1, duty),
|
||||
_ =>
|
||||
unreachable!(),
|
||||
};
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: PWM {} reconfigured to {}/{}",
|
||||
channel, pin.name(), duty, max
|
||||
);
|
||||
}
|
||||
Command::Pwm { duty, .. } if duty > 0xFFFF => {
|
||||
let _ = writeln!(socket, "error: PWM duty range must fit 16 bits");
|
||||
}
|
||||
Command::Pid { channel, parameter, value } => {
|
||||
// let pid = &mut states[channel].pid;
|
||||
// use command_parser::PidParameter::*;
|
||||
// match parameter {
|
||||
// Target =>
|
||||
// pid.set_target(value),
|
||||
// KP =>
|
||||
// pid.update_parameters(|parameters| parameters.kp = value),
|
||||
// KI =>
|
||||
// pid.update_parameters(|parameters| parameters.ki = value),
|
||||
// KD =>
|
||||
// pid.update_parameters(|parameters| parameters.kd = value),
|
||||
// OutputMin =>
|
||||
// pid.update_parameters(|parameters| parameters.output_min = value),
|
||||
// OutputMax =>
|
||||
// pid.update_parameters(|parameters| parameters.output_max = value),
|
||||
// IntegralMin =>
|
||||
// pid.update_parameters(|parameters| parameters.integral_min = value),
|
||||
// IntegralMax =>
|
||||
// pid.update_parameters(|parameters| parameters.integral_max = value),
|
||||
// }
|
||||
// pid.reset();
|
||||
// let _ = writeln!(socket, "PID parameter updated");
|
||||
let pid = &mut channel_states[channel].pid;
|
||||
use command_parser::PidParameter::*;
|
||||
match parameter {
|
||||
Target =>
|
||||
pid.target = value,
|
||||
KP =>
|
||||
pid.parameters.kp = value,
|
||||
KI =>
|
||||
pid.parameters.ki = value,
|
||||
KD =>
|
||||
pid.parameters.kd = value,
|
||||
OutputMin =>
|
||||
pid.parameters.output_min = value,
|
||||
OutputMax =>
|
||||
pid.parameters.output_max = value,
|
||||
IntegralMin =>
|
||||
pid.parameters.integral_min = value,
|
||||
IntegralMax =>
|
||||
pid.parameters.integral_max = value,
|
||||
}
|
||||
// TODO: really reset PID state
|
||||
// after each parameter change?
|
||||
pid.reset();
|
||||
let _ = writeln!(socket, "PID parameter updated");
|
||||
}
|
||||
Command::SteinhartHart { channel, parameter, value } => {
|
||||
// let sh = &mut states[channel].sh;
|
||||
// use command_parser::ShParameter::*;
|
||||
// match parameter {
|
||||
// A => sh.a = value,
|
||||
// B => sh.b = value,
|
||||
// C => sh.c = value,
|
||||
// ParallelR => sh.parallel_r = value,
|
||||
// }
|
||||
// let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
|
||||
let sh = &mut channel_states[channel].sh;
|
||||
use command_parser::ShParameter::*;
|
||||
match parameter {
|
||||
T0 => sh.t0 = value,
|
||||
B => sh.b = value,
|
||||
R0 => sh.r0 = value,
|
||||
}
|
||||
let _ = writeln!(socket, "Steinhart-Hart equation parameter updated");
|
||||
}
|
||||
Command::PostFilter { channel, rate } => {
|
||||
// let filter = ad7172::PostFilter::closest(rate);
|
||||
// match filter {
|
||||
// Some(filter) => {
|
||||
// adc.set_postfilter(channel as u8, Some(filter)).unwrap();
|
||||
// let _ = writeln!(
|
||||
// socket, "channel {}: postfilter set to {:.2} SPS",
|
||||
// channel, filter.output_rate().unwrap()
|
||||
// );
|
||||
// }
|
||||
// None => {
|
||||
// let _ = writeln!(socket, "Unable to choose postfilter");
|
||||
// }
|
||||
// }
|
||||
let filter = ad7172::PostFilter::closest(rate);
|
||||
match filter {
|
||||
Some(filter) => {
|
||||
adc.set_postfilter(channel as u8, Some(filter)).unwrap();
|
||||
let _ = writeln!(
|
||||
socket, "channel {}: postfilter set to {:.2} SPS",
|
||||
channel, filter.output_rate().unwrap()
|
||||
);
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(socket, "Unable to choose postfilter");
|
||||
}
|
||||
}
|
||||
}
|
||||
cmd => {
|
||||
let _ = writeln!(socket, "Not yet implemented: {:?}", cmd);
|
||||
}
|
||||
}
|
||||
Ok(SessionOutput::Error(e)) => {
|
||||
|
|
41
src/pid.rs
41
src/pid.rs
|
@ -9,12 +9,27 @@ pub struct Parameters {
|
|||
pub integral_max: f64
|
||||
}
|
||||
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
kp: 0.5,
|
||||
ki: 0.05,
|
||||
kd: 0.45,
|
||||
output_min: 0.0,
|
||||
output_max: 1.0,
|
||||
integral_min: 0.0,
|
||||
integral_max: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Controller {
|
||||
parameters: Parameters,
|
||||
target: f64,
|
||||
pub parameters: Parameters,
|
||||
pub target: f64,
|
||||
integral: f64,
|
||||
last_input: Option<f64>
|
||||
last_input: Option<f64>,
|
||||
pub last_output: Option<f64>,
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
|
@ -23,7 +38,8 @@ impl Controller {
|
|||
parameters: parameters,
|
||||
target: 0.0,
|
||||
last_input: None,
|
||||
integral: 0.0
|
||||
integral: 0.0,
|
||||
last_output: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,25 +70,10 @@ impl Controller {
|
|||
if output > self.parameters.output_max {
|
||||
output = self.parameters.output_max;
|
||||
}
|
||||
self.last_output = Some(output);
|
||||
output
|
||||
}
|
||||
|
||||
pub fn get_target(&self) -> f64 {
|
||||
self.target
|
||||
}
|
||||
|
||||
pub fn set_target(&mut self, target: f64) {
|
||||
self.target = target;
|
||||
}
|
||||
|
||||
pub fn get_parameters(&self) -> &Parameters {
|
||||
&self.parameters
|
||||
}
|
||||
|
||||
pub fn update_parameters<F: FnOnce(&mut Parameters)>(&mut self, f: F) {
|
||||
f(&mut self.parameters);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn reset(&mut self) {
|
||||
self.integral = 0.0;
|
||||
|
|
20
src/pins.rs
20
src/pins.rs
|
@ -1,7 +1,3 @@
|
|||
use embedded_hal::{
|
||||
blocking::spi::Transfer,
|
||||
digital::v2::OutputPin,
|
||||
};
|
||||
use stm32f4xx_hal::{
|
||||
gpio::{
|
||||
AF5, Alternate,
|
||||
|
@ -17,9 +13,9 @@ use stm32f4xx_hal::{
|
|||
},
|
||||
rcc::Clocks,
|
||||
pwm::{self, PwmChannels},
|
||||
spi::{self, Spi, NoMiso},
|
||||
spi::{Spi, NoMiso},
|
||||
stm32::{GPIOA, GPIOB, GPIOC, GPIOE, GPIOF, GPIOG, SPI2, SPI4, SPI5, TIM1, TIM3},
|
||||
time::{U32Ext, Hertz, MegaHertz},
|
||||
time::U32Ext,
|
||||
};
|
||||
|
||||
|
||||
|
@ -171,12 +167,12 @@ impl Pins {
|
|||
}
|
||||
|
||||
pub struct PwmPins {
|
||||
max_v0: PwmChannels<TIM3, pwm::C1>,
|
||||
max_v1: PwmChannels<TIM3, pwm::C2>,
|
||||
max_i_pos0: PwmChannels<TIM1, pwm::C1>,
|
||||
max_i_pos1: PwmChannels<TIM1, pwm::C2>,
|
||||
max_i_neg0: PwmChannels<TIM1, pwm::C3>,
|
||||
max_i_neg1: PwmChannels<TIM1, pwm::C4>,
|
||||
pub max_v0: PwmChannels<TIM3, pwm::C1>,
|
||||
pub max_v1: PwmChannels<TIM3, pwm::C2>,
|
||||
pub max_i_pos0: PwmChannels<TIM1, pwm::C1>,
|
||||
pub max_i_pos1: PwmChannels<TIM1, pwm::C2>,
|
||||
pub max_i_neg0: PwmChannels<TIM1, pwm::C3>,
|
||||
pub max_i_neg1: PwmChannels<TIM1, pwm::C4>,
|
||||
}
|
||||
|
||||
impl PwmPins {
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use core::fmt;
|
||||
use core::mem::MaybeUninit;
|
||||
use smoltcp::{
|
||||
iface::EthernetInterface,
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use core::ops::Deref;
|
||||
use super::command_parser::{Command, Error as ParserError};
|
||||
|
||||
const MAX_LINE_LEN: usize = 64;
|
||||
|
|
|
@ -1,41 +1,31 @@
|
|||
use lexical_core::Float;
|
||||
use num_traits::float::Float;
|
||||
|
||||
/// Steinhart-Hart equation parameters
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Parameters {
|
||||
pub t0: f64,
|
||||
pub r: f64,
|
||||
pub b: f64,
|
||||
pub r0: f64,
|
||||
|
||||
r_fact: f64,
|
||||
}
|
||||
|
||||
impl Parameters {
|
||||
/// Update the cached r_fact
|
||||
pub fn update(&mut self) {
|
||||
self.r_fact = (self.r / self.r0).ln();
|
||||
}
|
||||
|
||||
/// Perform the voltage to temperature conversion.
|
||||
///
|
||||
/// Result unit: Kelvin
|
||||
///
|
||||
/// TODO: verify
|
||||
pub fn get_temperature(&self, b: f64) -> f64 {
|
||||
let inv_temp = 1.0 / self.t0 + self.r_fact / b;
|
||||
pub fn get_temperature(&self, r: f64) -> f64 {
|
||||
let inv_temp = 1.0 / self.t0 + (r / self.r0).ln() / self.b;
|
||||
1.0 / inv_temp
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
let mut p = Parameters {
|
||||
Parameters {
|
||||
t0: 0.001_4,
|
||||
r: 0.000_000_099,
|
||||
b: 0.000_000_099,
|
||||
r0: 5_110.0,
|
||||
r_fact: 0.0,
|
||||
};
|
||||
p.update();
|
||||
p
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue