Compare commits

..

6 Commits

Author SHA1 Message Date
Astro a4dde1b8ca delint 2020-09-18 00:55:53 +02:00
Astro 7361619a53 pid: update default parameters 2020-09-18 00:41:32 +02:00
Astro 34543c8660 pid: only reset after target change 2020-09-18 00:24:00 +02:00
Astro 83a209397e fix tests 2020-09-18 00:23:30 +02:00
Astro ba84295ec5 reconnect the pid controller 2020-09-18 00:09:30 +02:00
Astro fb81380955 fix tests
run with `cargo test --target=x86_64-unknown-linux-gnu`
2020-09-17 01:48:27 +02:00
10 changed files with 49 additions and 102 deletions

View File

@ -100,24 +100,6 @@ impl<SPI: Transfer<u8, Error = E>, NSS: OutputPin, E: fmt::Debug> Adc<SPI, NSS>
Ok(()) Ok(())
} }
pub fn disable_channel(
&mut self, index: u8
) -> Result<(), SPI::Error> {
self.update_reg(&regs::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(&regs::Channel { index }, |data| {
data.set_enabled(false);
})?;
}
Ok(())
}
pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> { pub fn get_calibration(&mut self, index: u8) -> Result<ChannelCalibration, SPI::Error> {
let offset = self.read_reg(&regs::Offset { index })?.offset(); let offset = self.read_reg(&regs::Offset { index })?.offset();
let gain = self.read_reg(&regs::Gain { index })?.gain(); let gain = self.read_reg(&regs::Gain { index })?.gain();

View File

@ -19,8 +19,6 @@ pub const SPI_MODE: spi::Mode = spi::Mode {
/// 2 MHz /// 2 MHz
pub const SPI_CLOCK: MegaHertz = MegaHertz(2); pub const SPI_CLOCK: MegaHertz = MegaHertz(2);
pub const MAX_VALUE: u32 = 0xFF_FFFF;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
#[repr(u8)] #[repr(u8)]

View File

@ -7,7 +7,7 @@ use uom::si::{
}, },
electric_potential::volt, electric_potential::volt,
electrical_resistance::ohm, electrical_resistance::ohm,
temperature_interval::kelvin, thermodynamic_temperature::degree_celsius,
}; };
use crate::{ use crate::{
ad7172, ad7172,
@ -51,10 +51,11 @@ impl ChannelState {
} }
/// Update PID state on ADC input, calculate new DAC output /// Update PID state on ADC input, calculate new DAC output
pub fn update_pid(&mut self) { pub fn update_pid(&mut self) -> Option<f64> {
// Update PID controller let temperature = self.get_temperature()?
// self.pid.update(self.get_temperature().unwrap().get::<kelvin>()) .get::<degree_celsius>();
// TODO: add output field let pid_output = self.pid.update(temperature);
Some(pid_output)
} }
pub fn get_adc(&self) -> Option<ElectricPotential> { pub fn get_adc(&self) -> Option<ElectricPotential> {

View File

@ -45,8 +45,8 @@ impl Channels {
.expect("adc_calibration1"); .expect("adc_calibration1");
adc.start_continuous_conversion().unwrap(); adc.start_continuous_conversion().unwrap();
let mut channel0 = Channel::new(pins.channel0, adc_calibration0); let channel0 = Channel::new(pins.channel0, adc_calibration0);
let mut channel1 = Channel::new(pins.channel1, adc_calibration1); let channel1 = Channel::new(pins.channel1, adc_calibration1);
let pins_adc = pins.pins_adc; let pins_adc = pins.pins_adc;
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 };
@ -70,21 +70,16 @@ impl Channels {
self.adc.data_ready().unwrap().map(|channel| { self.adc.data_ready().unwrap().map(|channel| {
let data = self.adc.read_data().unwrap(); let data = self.adc.read_data().unwrap();
let dac_value = {
let state = self.channel_state(channel); let state = self.channel_state(channel);
state.update(instant, data);
let pid_output = state.update_pid();
if state.pid_engaged { state.update(instant, data);
Some(pid_output) match state.update_pid() {
} else { Some(pid_output) if state.pid_engaged => {
None log::info!("PID: {:.3} A", pid_output);
}
};
if let Some(dac_value) = dac_value {
// Forward PID output to i_set DAC // Forward PID output to i_set DAC
// TODO: self.set_i(channel.into(), ElectricCurrent::new::<ampere>(pid_output));
// self.set_dac(channel.into(), ElectricPotential::new::<volt>(dac_value)); }
_ => {}
} }
channel channel
@ -169,7 +164,6 @@ impl Channels {
let mut prev = self.read_dac_feedback(channel); let mut prev = self.read_dac_feedback(channel);
loop { loop {
let current = self.read_dac_feedback(channel); let current = self.read_dac_feedback(channel);
use num_traits::float::Float;
if (current - prev).abs() < tolerance { if (current - prev).abs() < tolerance {
return current; return current;
} }

View File

@ -122,17 +122,6 @@ pub enum PwmPin {
MaxV, 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)] #[derive(Debug, Clone, PartialEq)]
pub enum Command { pub enum Command {
Quit, Quit,
@ -178,19 +167,6 @@ fn whitespace(input: &[u8]) -> IResult<&[u8], ()> {
fold_many1(char(' '), (), |(), _| ())(input) 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>> { fn float(input: &[u8]) -> IResult<&[u8], Result<f64, Error>> {
let (input, sign) = opt(is_a("-"))(input)?; let (input, sign) = opt(is_a("-"))(input)?;
let negative = sign.is_some(); let negative = sign.is_some();
@ -461,7 +437,7 @@ mod test {
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 1, channel: 1,
pin: PwmPin::ISet, pin: PwmPin::ISet,
value: 16383, value: 16383.0,
})); }));
} }
@ -470,7 +446,6 @@ mod test {
let command = Command::parse(b"pwm 0 pid"); let command = Command::parse(b"pwm 0 pid");
assert_eq!(command, Ok(Command::PwmPid { assert_eq!(command, Ok(Command::PwmPid {
channel: 0, channel: 0,
pin: PwmPin::ISet,
})); }));
} }
@ -480,7 +455,7 @@ mod test {
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxIPos, pin: PwmPin::MaxIPos,
value: 7, value: 7.0,
})); }));
} }
@ -490,7 +465,7 @@ mod test {
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxINeg, pin: PwmPin::MaxINeg,
value: 128, value: 128.0,
})); }));
} }
@ -500,7 +475,7 @@ mod test {
assert_eq!(command, Ok(Command::Pwm { assert_eq!(command, Ok(Command::Pwm {
channel: 0, channel: 0,
pin: PwmPin::MaxV, pin: PwmPin::MaxV,
value: 32768, value: 32768.0,
})); }));
} }

View File

@ -5,7 +5,7 @@ static USB_LOGGER: usb::Logger = usb::Logger;
#[cfg(not(feature = "semihosting"))] #[cfg(not(feature = "semihosting"))]
pub fn init_log() { pub fn init_log() {
log::set_logger(&USB_LOGGER); let _ = log::set_logger(&USB_LOGGER);
} }
#[cfg(feature = "semihosting")] #[cfg(feature = "semihosting")]

View File

@ -1,24 +1,20 @@
#![no_std] #![cfg_attr(not(test), no_std)]
#![no_main] #![cfg_attr(not(test), no_main)]
#![feature(maybe_uninit_extra, maybe_uninit_ref)] #![feature(maybe_uninit_extra, maybe_uninit_ref)]
// TODO: #![deny(warnings, unused)] // TODO: #![deny(warnings, unused)]
#[cfg(not(feature = "semihosting"))] #[cfg(not(any(feature = "semihosting", test)))]
use panic_abort as _; use panic_abort as _;
#[cfg(feature = "semihosting")] #[cfg(all(feature = "semihosting", not(test)))]
use panic_semihosting as _; use panic_semihosting as _;
use log::{info, warn}; use log::{info, warn};
use core::ops::DerefMut;
use core::fmt::Write; use core::fmt::Write;
use cortex_m::asm::wfi; use cortex_m::asm::wfi;
use cortex_m_rt::entry; use cortex_m_rt::entry;
use stm32f4xx_hal::{ use stm32f4xx_hal::{
hal::{ hal::watchdog::{WatchdogEnable, Watchdog},
self,
watchdog::{WatchdogEnable, Watchdog},
},
rcc::RccExt, rcc::RccExt,
watchdog::IndependentWatchdog, watchdog::IndependentWatchdog,
time::{U32Ext, MegaHertz}, time::{U32Ext, MegaHertz},
@ -80,6 +76,7 @@ const TCP_PORT: u16 = 23;
/// Initialization and main loop /// Initialization and main loop
#[cfg(not(test))]
#[entry] #[entry]
fn main() -> ! { fn main() -> ! {
init_log(); init_log();
@ -199,7 +196,7 @@ fn main() -> ! {
let state = channels.channel_state(channel); let state = channels.channel_state(channel);
let _ = writeln!(socket, "PID settings for channel {}", channel); let _ = writeln!(socket, "PID settings for channel {}", channel);
let pid = &state.pid; let pid = &state.pid;
let _ = writeln!(socket, "- target={:.4}", pid.target); let _ = writeln!(socket, "- target={:.4} °C", pid.target);
macro_rules! show_pid_parameter { macro_rules! show_pid_parameter {
($p: tt) => { ($p: tt) => {
let _ = writeln!( let _ = writeln!(
@ -216,7 +213,7 @@ fn main() -> ! {
show_pid_parameter!(output_min); show_pid_parameter!(output_min);
show_pid_parameter!(output_max); show_pid_parameter!(output_max);
if let Some(last_output) = pid.last_output { 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, ""); let _ = writeln!(socket, "");
} }
@ -352,16 +349,17 @@ fn main() -> ! {
max.into_format_args(ampere, Abbreviation), max.into_format_args(ampere, Abbreviation),
); );
} }
_ =>
unreachable!(),
} }
} }
Command::Pid { channel, parameter, value } => { Command::Pid { channel, parameter, value } => {
let pid = &mut channels.channel_state(channel).pid; let pid = &mut channels.channel_state(channel).pid;
use command_parser::PidParameter::*; use command_parser::PidParameter::*;
match parameter { match parameter {
Target => Target => {
pid.target = value, pid.target = value;
// reset pid.integral
pid.reset();
}
KP => KP =>
pid.parameters.kp = value, pid.parameters.kp = value,
KI => KI =>
@ -377,9 +375,6 @@ fn main() -> ! {
IntegralMax => IntegralMax =>
pid.parameters.integral_max = value, pid.parameters.integral_max = value,
} }
// TODO: really reset PID state
// after each parameter change?
pid.reset();
let _ = writeln!(socket, "PID parameter updated"); let _ = writeln!(socket, "PID parameter updated");
} }
Command::SteinhartHart { channel, parameter, value } => { Command::SteinhartHart { channel, parameter, value } => {

View File

@ -12,13 +12,13 @@ pub struct Parameters {
impl Default for Parameters { impl Default for Parameters {
fn default() -> Self { fn default() -> Self {
Parameters { Parameters {
kp: 0.5, kp: 1.5,
ki: 0.05, ki: 0.1,
kd: 0.45, kd: 150.0,
output_min: 0.0, output_min: 0.0,
output_max: 5.0, output_max: 2.0,
integral_min: 0.0, integral_min: -10.0,
integral_max: 1.0, integral_max: 10.0,
} }
} }
} }
@ -44,10 +44,13 @@ impl Controller {
} }
pub fn update(&mut self, input: f64) -> f64 { pub fn update(&mut self, input: f64) -> f64 {
// error
let error = self.target - input; let error = self.target - input;
// partial
let p = self.parameters.kp * error; let p = self.parameters.kp * error;
//integral
self.integral += error; self.integral += error;
if self.integral < self.parameters.integral_min { if self.integral < self.parameters.integral_min {
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; let i = self.parameters.ki * self.integral;
// derivative
let d = match self.last_input { let d = match self.last_input {
None => 0.0, None => 0.0,
Some(last_input) => self.parameters.kd * (last_input - input) Some(last_input) => self.parameters.kd * (last_input - input)
}; };
self.last_input = Some(input); self.last_input = Some(input);
// output
let mut output = p + i + d; let mut output = p + i + d;
if output < self.parameters.output_min { if output < self.parameters.output_min {
output = self.parameters.output_min; output = self.parameters.output_min;
@ -103,7 +108,7 @@ mod test {
const DELAY: usize = 10; const DELAY: usize = 10;
let mut pid = Controller::new(PARAMETERS.clone()); let mut pid = Controller::new(PARAMETERS.clone());
pid.set_target(TARGET); pid.target = TARGET;
let mut values = [DEFAULT; DELAY]; let mut values = [DEFAULT; DELAY];
let mut t = 0; let mut t = 0;
@ -118,6 +123,5 @@ mod test {
t = next_t; t = next_t;
total_t += 1; total_t += 1;
} }
dbg!(values[t], total_t);
} }
} }

View File

@ -6,13 +6,11 @@ use stm32f4xx_hal::{
gpioa::*, gpioa::*,
gpiob::*, gpiob::*,
gpioc::*, gpioc::*,
gpiod::*,
gpioe::*, gpioe::*,
gpiof::*, gpiof::*,
gpiog::*, gpiog::*,
GpioExt, GpioExt,
Output, PushPull, Output, PushPull,
Speed::VeryHigh,
}, },
otg_fs::USB, otg_fs::USB,
rcc::Clocks, rcc::Clocks,

View File

@ -6,10 +6,10 @@ use stm32f4xx_hal::{
}; };
use usb_device::{ use usb_device::{
class_prelude::{UsbBusAllocator}, class_prelude::{UsbBusAllocator},
prelude::{UsbError, UsbDevice, UsbDeviceBuilder, UsbVidPid}, prelude::{UsbDevice, UsbDeviceBuilder, UsbVidPid},
}; };
use usbd_serial::SerialPort; use usbd_serial::SerialPort;
use log::{Record, Level, Log, Metadata}; use log::{Record, Log, Metadata};
static mut EP_MEMORY: [u32; 1024] = [0; 1024]; static mut EP_MEMORY: [u32; 1024] = [0; 1024];
@ -30,7 +30,7 @@ impl State {
let serial = SerialPort::new(bus); let serial = SerialPort::new(bus);
let dev = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd)) let dev = UsbDeviceBuilder::new(bus, UsbVidPid(0x16c0, 0x27dd))
.manufacturer("M-Labs") .manufacturer("M-Labs")
.product("thermostat") .product("tecpak")
.device_release(0x20) .device_release(0x20)
.self_powered(true) .self_powered(true)
.device_class(usbd_serial::USB_CLASS_CDC) .device_class(usbd_serial::USB_CLASS_CDC)