Compare commits
6 Commits
94e0525002
...
a4dde1b8ca
Author | SHA1 | Date |
---|---|---|
Astro | a4dde1b8ca | |
Astro | 7361619a53 | |
Astro | 34543c8660 | |
Astro | 83a209397e | |
Astro | ba84295ec5 | |
Astro | fb81380955 |
|
@ -100,24 +100,6 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_channel(
|
||||
&mut self, index: u8
|
||||
) -> Result<(), SPI::Error> {
|
||||
self.update_reg(®s::Channel { index }, |data| {
|
||||
data.set_enabled(false);
|
||||
})?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn disable_all_channels(&mut self) -> Result<(), SPI::Error> {
|
||||
for index in 0..4 {
|
||||
self.update_reg(®s::Channel { index }, |data| {
|
||||
data.set_enabled(false);
|
||||
})?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> {
|
||||
let offset = self.read_reg(®s::Offset { index })?.offset();
|
||||
let gain = self.read_reg(®s::Gain { index })?.gain();
|
||||
|
|
|
@ -19,8 +19,6 @@ 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, Copy, Debug)]
|
||||
#[repr(u8)]
|
||||
|
|
|
@ -7,7 +7,7 @@ use uom::si::{
|
|||
},
|
||||
electric_potential::volt,
|
||||
electrical_resistance::ohm,
|
||||
temperature_interval::kelvin,
|
||||
thermodynamic_temperature::degree_celsius,
|
||||
};
|
||||
use crate::{
|
||||
ad7172,
|
||||
|
@ -51,10 +51,11 @@ impl ChannelState {
|
|||
}
|
||||
|
||||
/// Update PID state on ADC input, calculate new DAC output
|
||||
pub fn update_pid(&mut self) {
|
||||
// Update PID controller
|
||||
// self.pid.update(self.get_temperature().unwrap().get::<kelvin>())
|
||||
// TODO: add output field
|
||||
pub fn update_pid(&mut self) -> Option<f64> {
|
||||
let temperature = self.get_temperature()?
|
||||
.get::<degree_celsius>();
|
||||
let pid_output = self.pid.update(temperature);
|
||||
Some(pid_output)
|
||||
}
|
||||
|
||||
pub fn get_adc(&self) -> Option<ElectricPotential> {
|
||||
|
|
|
@ -45,8 +45,8 @@ impl Channels {
|
|||
.expect("adc_calibration1");
|
||||
adc.start_continuous_conversion().unwrap();
|
||||
|
||||
let mut channel0 = Channel::new(pins.channel0, adc_calibration0);
|
||||
let mut channel1 = Channel::new(pins.channel1, adc_calibration1);
|
||||
let channel0 = Channel::new(pins.channel0, adc_calibration0);
|
||||
let channel1 = Channel::new(pins.channel1, adc_calibration1);
|
||||
let pins_adc = pins.pins_adc;
|
||||
let pwm = pins.pwm;
|
||||
let mut channels = Channels { channel0, channel1, adc, pins_adc, pwm };
|
||||
|
@ -70,21 +70,16 @@ impl Channels {
|
|||
self.adc.data_ready().unwrap().map(|channel| {
|
||||
let data = self.adc.read_data().unwrap();
|
||||
|
||||
let dac_value = {
|
||||
let state = self.channel_state(channel);
|
||||
state.update(instant, data);
|
||||
let pid_output = state.update_pid();
|
||||
|
||||
if state.pid_engaged {
|
||||
Some(pid_output)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
if let Some(dac_value) = dac_value {
|
||||
state.update(instant, data);
|
||||
match state.update_pid() {
|
||||
Some(pid_output) if state.pid_engaged => {
|
||||
log::info!("PID: {:.3} A", pid_output);
|
||||
// Forward PID output to i_set DAC
|
||||
// TODO:
|
||||
// self.set_dac(channel.into(), ElectricPotential::new::<volt>(dac_value));
|
||||
self.set_i(channel.into(), ElectricCurrent::new::<ampere>(pid_output));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
channel
|
||||
|
@ -169,7 +164,6 @@ impl Channels {
|
|||
let mut prev = self.read_dac_feedback(channel);
|
||||
loop {
|
||||
let current = self.read_dac_feedback(channel);
|
||||
use num_traits::float::Float;
|
||||
if (current - prev).abs() < tolerance {
|
||||
return current;
|
||||
}
|
||||
|
|
|
@ -122,17 +122,6 @@ pub enum PwmPin {
|
|||
MaxV,
|
||||
}
|
||||
|
||||
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)]
|
||||
pub enum Command {
|
||||
Quit,
|
||||
|
@ -178,19 +167,6 @@ fn whitespace(input: &[u8]) -> IResult<&[u8], ()> {
|
|||
fold_many1(char(' '), (), |(), _| ())(input)
|
||||
}
|
||||
|
||||
fn unsigned(input: &[u8]) -> IResult<&[u8], Result<u32, Error>> {
|
||||
take_while1(is_digit)(input)
|
||||
.map(|(input, digits)| {
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
|
||||
let (input, sign) = opt(is_a("-"))(input)?;
|
||||
let negative = sign.is_some();
|
||||
|
@ -461,7 +437,7 @@ mod test {
|
|||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 1,
|
||||
pin: PwmPin::ISet,
|
||||
value: 16383,
|
||||
value: 16383.0,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -470,7 +446,6 @@ mod test {
|
|||
let command = Command::parse(b"pwm 0 pid");
|
||||
assert_eq!(command, Ok(Command::PwmPid {
|
||||
channel: 0,
|
||||
pin: PwmPin::ISet,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -480,7 +455,7 @@ mod test {
|
|||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
pin: PwmPin::MaxIPos,
|
||||
value: 7,
|
||||
value: 7.0,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -490,7 +465,7 @@ mod test {
|
|||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
pin: PwmPin::MaxINeg,
|
||||
value: 128,
|
||||
value: 128.0,
|
||||
}));
|
||||
}
|
||||
|
||||
|
@ -500,7 +475,7 @@ mod test {
|
|||
assert_eq!(command, Ok(Command::Pwm {
|
||||
channel: 0,
|
||||
pin: PwmPin::MaxV,
|
||||
value: 32768,
|
||||
value: 32768.0,
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ static USB_LOGGER: usb::Logger = usb::Logger;
|
|||
|
||||
#[cfg(not(feature = "semihosting"))]
|
||||
pub fn init_log() {
|
||||
log::set_logger(&USB_LOGGER);
|
||||
let _ = log::set_logger(&USB_LOGGER);
|
||||
}
|
||||
|
||||
#[cfg(feature = "semihosting")]
|
||||
|
|
31
src/main.rs
31
src/main.rs
|
@ -1,24 +1,20 @@
|
|||
#![no_std]
|
||||
#![no_main]
|
||||
#![cfg_attr(not(test), no_std)]
|
||||
#![cfg_attr(not(test), no_main)]
|
||||
#![feature(maybe_uninit_extra, maybe_uninit_ref)]
|
||||
// TODO: #![deny(warnings, unused)]
|
||||
|
||||
#[cfg(not(feature = "semihosting"))]
|
||||
#[cfg(not(any(feature = "semihosting", test)))]
|
||||
use panic_abort as _;
|
||||
#[cfg(feature = "semihosting")]
|
||||
#[cfg(all(feature = "semihosting", not(test)))]
|
||||
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 stm32f4xx_hal::{
|
||||
hal::{
|
||||
self,
|
||||
watchdog::{WatchdogEnable, Watchdog},
|
||||
},
|
||||
hal::watchdog::{WatchdogEnable, Watchdog},
|
||||
rcc::RccExt,
|
||||
watchdog::IndependentWatchdog,
|
||||
time::{U32Ext, MegaHertz},
|
||||
|
@ -80,6 +76,7 @@ const TCP_PORT: u16 = 23;
|
|||
|
||||
|
||||
/// Initialization and main loop
|
||||
#[cfg(not(test))]
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
init_log();
|
||||
|
@ -199,7 +196,7 @@ fn main() -> ! {
|
|||
let state = channels.channel_state(channel);
|
||||
let _ = writeln!(socket, "PID settings for channel {}", channel);
|
||||
let pid = &state.pid;
|
||||
let _ = writeln!(socket, "- target={:.4}", pid.target);
|
||||
let _ = writeln!(socket, "- target={:.4} °C", pid.target);
|
||||
macro_rules! show_pid_parameter {
|
||||
($p: tt) => {
|
||||
let _ = writeln!(
|
||||
|
@ -216,7 +213,7 @@ fn main() -> ! {
|
|||
show_pid_parameter!(output_min);
|
||||
show_pid_parameter!(output_max);
|
||||
if let Some(last_output) = pid.last_output {
|
||||
let _ = writeln!(socket, "- last_output={:.4}", last_output);
|
||||
let _ = writeln!(socket, "- last_output={:.3} A", last_output);
|
||||
}
|
||||
let _ = writeln!(socket, "");
|
||||
}
|
||||
|
@ -352,16 +349,17 @@ fn main() -> ! {
|
|||
max.into_format_args(ampere, Abbreviation),
|
||||
);
|
||||
}
|
||||
_ =>
|
||||
unreachable!(),
|
||||
}
|
||||
}
|
||||
Command::Pid { channel, parameter, value } => {
|
||||
let pid = &mut channels.channel_state(channel).pid;
|
||||
use command_parser::PidParameter::*;
|
||||
match parameter {
|
||||
Target =>
|
||||
pid.target = value,
|
||||
Target => {
|
||||
pid.target = value;
|
||||
// reset pid.integral
|
||||
pid.reset();
|
||||
}
|
||||
KP =>
|
||||
pid.parameters.kp = value,
|
||||
KI =>
|
||||
|
@ -377,9 +375,6 @@ fn main() -> ! {
|
|||
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 } => {
|
||||
|
|
20
src/pid.rs
20
src/pid.rs
|
@ -12,13 +12,13 @@ pub struct Parameters {
|
|||
impl Default for Parameters {
|
||||
fn default() -> Self {
|
||||
Parameters {
|
||||
kp: 0.5,
|
||||
ki: 0.05,
|
||||
kd: 0.45,
|
||||
kp: 1.5,
|
||||
ki: 0.1,
|
||||
kd: 150.0,
|
||||
output_min: 0.0,
|
||||
output_max: 5.0,
|
||||
integral_min: 0.0,
|
||||
integral_max: 1.0,
|
||||
output_max: 2.0,
|
||||
integral_min: -10.0,
|
||||
integral_max: 10.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,10 +44,13 @@ impl Controller {
|
|||
}
|
||||
|
||||
pub fn update(&mut self, input: f64) -> f64 {
|
||||
// error
|
||||
let error = self.target - input;
|
||||
|
||||
// partial
|
||||
let p = self.parameters.kp * error;
|
||||
|
||||
//integral
|
||||
self.integral += error;
|
||||
if self.integral < self.parameters.integral_min {
|
||||
self.integral = self.parameters.integral_min;
|
||||
|
@ -57,12 +60,14 @@ impl Controller {
|
|||
}
|
||||
let i = self.parameters.ki * self.integral;
|
||||
|
||||
// derivative
|
||||
let d = match self.last_input {
|
||||
None => 0.0,
|
||||
Some(last_input) => self.parameters.kd * (last_input - input)
|
||||
};
|
||||
self.last_input = Some(input);
|
||||
|
||||
// output
|
||||
let mut output = p + i + d;
|
||||
if output < self.parameters.output_min {
|
||||
output = self.parameters.output_min;
|
||||
|
@ -103,7 +108,7 @@ mod test {
|
|||
const DELAY: usize = 10;
|
||||
|
||||
let mut pid = Controller::new(PARAMETERS.clone());
|
||||
pid.set_target(TARGET);
|
||||
pid.target = TARGET;
|
||||
|
||||
let mut values = [DEFAULT; DELAY];
|
||||
let mut t = 0;
|
||||
|
@ -118,6 +123,5 @@ mod test {
|
|||
t = next_t;
|
||||
total_t += 1;
|
||||
}
|
||||
dbg!(values[t], total_t);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,13 +6,11 @@ use stm32f4xx_hal::{
|
|||
gpioa::*,
|
||||
gpiob::*,
|
||||
gpioc::*,
|
||||
gpiod::*,
|
||||
gpioe::*,
|
||||
gpiof::*,
|
||||
gpiog::*,
|
||||
GpioExt,
|
||||
Output, PushPull,
|
||||
Speed::VeryHigh,
|
||||
},
|
||||
otg_fs::USB,
|
||||
rcc::Clocks,
|
||||
|
|
|
@ -6,10 +6,10 @@ use stm32f4xx_hal::{
|
|||
};
|
||||
use usb_device::{
|
||||
class_prelude::{UsbBusAllocator},
|
||||
prelude::{UsbError, UsbDevice, UsbDeviceBuilder, UsbVidPid},
|
||||
prelude::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
|
||||
};
|
||||
use usbd_serial::SerialPort;
|
||||
use log::{Record, Level, Log, Metadata};
|
||||
use log::{Record, Log, Metadata};
|
||||
|
||||
static mut EP_MEMORY: [u32; 1024] = [0; 1024];
|
||||
|
||||
|
@ -30,7 +30,7 @@ impl State {
|
|||
let serial = SerialPort::new(bus);
|
||||
let dev = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
|
||||
.manufacturer("M-Labs")
|
||||
.product("thermostat")
|
||||
.product("tecpak")
|
||||
.device_release(0x20)
|
||||
.self_powered(true)
|
||||
.device_class(usbd_serial::USB_CLASS_CDC)
|
||||
|
|
Loading…
Reference in New Issue