forked from M-Labs/ionpak-thermostat
add electrometer, introduce *Status objects
This commit is contained in:
parent
c676102b33
commit
44d95973ca
|
@ -37,6 +37,10 @@ pub const FD_ADC_GAIN: f32 = 3111.1111111111104;
|
||||||
pub const FD_ADC_OFFSET: f32 = 96.0;
|
pub const FD_ADC_OFFSET: f32 = 96.0;
|
||||||
pub const FBV_ADC_GAIN: f32 = 49.13796058269066;
|
pub const FBV_ADC_GAIN: f32 = 49.13796058269066;
|
||||||
pub const FBV_PWM_GAIN: f32 = 0.5730803571428571;
|
pub const FBV_PWM_GAIN: f32 = 0.5730803571428571;
|
||||||
|
pub const IC_ADC_GAIN_LOW: f32 = 1333333333333.3333;
|
||||||
|
pub const IC_ADC_GAIN_MED: f32 = 13201320132.0132;
|
||||||
|
pub const IC_ADC_GAIN_HIGH: f32 = 133320001.3332;
|
||||||
|
pub const IC_ADC_OFFSET: f32 = 96.0;
|
||||||
|
|
||||||
pub const FBI_R223: f32 = 200.0;
|
pub const FBI_R223: f32 = 200.0;
|
||||||
pub const FBI_R224: f32 = 39.0;
|
pub const FBI_R224: f32 = 39.0;
|
||||||
|
@ -80,7 +84,6 @@ pub fn set_fbv_pwm(duty: u16) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum EmissionRange {
|
pub enum EmissionRange {
|
||||||
Low, // 22K
|
Low, // 22K
|
||||||
|
@ -102,6 +105,27 @@ pub fn set_emission_range(range: EmissionRange) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ElectrometerRange {
|
||||||
|
Low, // 1G
|
||||||
|
Med, // 1G//10M
|
||||||
|
High // 1G//100K
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_electrometer_range(range: ElectrometerRange) {
|
||||||
|
cortex_m::interrupt::free(|cs| {
|
||||||
|
let gpio_p = tm4c129x::GPIO_PORTP.borrow(cs);
|
||||||
|
gpio_p.data.modify(|r, w| {
|
||||||
|
let value = r.data().bits() & 0b111100;
|
||||||
|
match range {
|
||||||
|
ElectrometerRange::Low => w.data().bits(value | 0b000000),
|
||||||
|
ElectrometerRange::Med => w.data().bits(value | 0b000001),
|
||||||
|
ElectrometerRange::High => w.data().bits(value | 0b000010),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
pub fn reset_error() {
|
pub fn reset_error() {
|
||||||
cortex_m::interrupt::free(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
let gpio_q = tm4c129x::GPIO_PORTQ.borrow(cs);
|
let gpio_q = tm4c129x::GPIO_PORTQ.borrow(cs);
|
||||||
|
@ -201,6 +225,8 @@ pub fn init() {
|
||||||
let gpio_p = tm4c129x::GPIO_PORTP.borrow(cs);
|
let gpio_p = tm4c129x::GPIO_PORTP.borrow(cs);
|
||||||
gpio_p.dir.write(|w| w.dir().bits(0b111111));
|
gpio_p.dir.write(|w| w.dir().bits(0b111111));
|
||||||
gpio_p.den.write(|w| w.den().bits(0b111111));
|
gpio_p.den.write(|w| w.den().bits(0b111111));
|
||||||
|
set_emission_range(EmissionRange::Med);
|
||||||
|
set_electrometer_range(ElectrometerRange::Med);
|
||||||
|
|
||||||
// Set up error pins
|
// Set up error pins
|
||||||
let gpio_l = tm4c129x::GPIO_PORTL.borrow(cs);
|
let gpio_l = tm4c129x::GPIO_PORTL.borrow(cs);
|
||||||
|
@ -209,7 +235,7 @@ pub fn init() {
|
||||||
gpio_l.den.write(|w| w.den().bits(FV_ERRN|FBV_ERRN|FBI_ERRN|AV_ERRN|AI_ERRN|ERR_LATCHN));
|
gpio_l.den.write(|w| w.den().bits(FV_ERRN|FBV_ERRN|FBI_ERRN|AV_ERRN|AI_ERRN|ERR_LATCHN));
|
||||||
gpio_q.dir.write(|w| w.dir().bits(ERR_RESN));
|
gpio_q.dir.write(|w| w.dir().bits(ERR_RESN));
|
||||||
gpio_q.den.write(|w| w.den().bits(ERR_RESN));
|
gpio_q.den.write(|w| w.den().bits(ERR_RESN));
|
||||||
reset_error();
|
reset_error(); // error latch is an undefined state upon power-up; reset it
|
||||||
|
|
||||||
// Set up PWMs
|
// Set up PWMs
|
||||||
let gpio_f = tm4c129x::GPIO_PORTF_AHB.borrow(cs);
|
let gpio_f = tm4c129x::GPIO_PORTF_AHB.borrow(cs);
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
use board;
|
||||||
|
|
||||||
|
pub struct Electrometer {
|
||||||
|
range: board::ElectrometerRange,
|
||||||
|
ic_buffer: [f32; 16],
|
||||||
|
ic_buffer_count: usize,
|
||||||
|
last_ic: Option<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ElectrometerStatus {
|
||||||
|
pub ic: Option<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Electrometer {
|
||||||
|
pub const fn new() -> Electrometer {
|
||||||
|
Electrometer {
|
||||||
|
range: board::ElectrometerRange::Med,
|
||||||
|
ic_buffer: [0.0; 16],
|
||||||
|
ic_buffer_count: 0,
|
||||||
|
last_ic: None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn adc_input(&mut self, ic_sample: u16) {
|
||||||
|
let gain = match self.range {
|
||||||
|
board::ElectrometerRange::Low => board::IC_ADC_GAIN_LOW,
|
||||||
|
board::ElectrometerRange::Med => board::IC_ADC_GAIN_MED,
|
||||||
|
board::ElectrometerRange::High => board::IC_ADC_GAIN_HIGH
|
||||||
|
};
|
||||||
|
self.ic_buffer[self.ic_buffer_count] = ((ic_sample as f32) - board::IC_ADC_OFFSET)/gain;
|
||||||
|
self.ic_buffer_count += 1;
|
||||||
|
if self.ic_buffer_count == self.ic_buffer.len() {
|
||||||
|
let mut ic_avg: f32 = 0.0;
|
||||||
|
for ic in self.ic_buffer.iter() {
|
||||||
|
ic_avg += *ic;
|
||||||
|
}
|
||||||
|
self.last_ic = Some(ic_avg/(self.ic_buffer.len() as f32));
|
||||||
|
self.ic_buffer_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_status(&self) -> ElectrometerStatus {
|
||||||
|
ElectrometerStatus {
|
||||||
|
ic: self.last_ic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ElectrometerStatus {
|
||||||
|
pub fn debug_print(&self) {
|
||||||
|
if self.ic.is_some() {
|
||||||
|
println!("ion current: {}nA", 1e9*self.ic.unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,9 +1,11 @@
|
||||||
|
use core::num::Float;
|
||||||
|
|
||||||
use board;
|
use board;
|
||||||
use pid;
|
use pid;
|
||||||
|
|
||||||
const PID_PARAMETERS: pid::Parameters = pid::Parameters {
|
const PID_PARAMETERS: pid::Parameters = pid::Parameters {
|
||||||
kp: 0.004,
|
kp: 0.027,
|
||||||
ki: 0.002,
|
ki: 0.013,
|
||||||
kd: 0.0,
|
kd: 0.0,
|
||||||
output_min: 0.0,
|
output_min: 0.0,
|
||||||
output_max: 30.0,
|
output_max: 30.0,
|
||||||
|
@ -13,38 +15,63 @@ const PID_PARAMETERS: pid::Parameters = pid::Parameters {
|
||||||
|
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
pid: pid::Controller
|
pid: pid::Controller,
|
||||||
|
target: f32,
|
||||||
|
last_av: Option<f32>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ControllerStatus {
|
||||||
|
pub ready: bool,
|
||||||
|
pub av: Option<f32>
|
||||||
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub const fn new() -> Controller {
|
pub const fn new() -> Controller {
|
||||||
Controller {
|
Controller {
|
||||||
pid: pid::Controller::new(PID_PARAMETERS)
|
pid: pid::Controller::new(PID_PARAMETERS),
|
||||||
|
target: 0.0,
|
||||||
|
last_av: None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn adc_input(&mut self, av_sample: u16) {
|
pub fn adc_input(&mut self, av_sample: u16) {
|
||||||
let pid_out = self.pid.update(av_sample as f32);
|
let av = av_sample as f32/board::AV_ADC_GAIN;
|
||||||
|
self.last_av = Some(av);
|
||||||
|
|
||||||
|
let pid_out = self.pid.update(av);
|
||||||
board::set_hv_pwm(pid_out as u16)
|
board::set_hv_pwm(pid_out as u16)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target(&mut self, volts: f32) {
|
pub fn set_target(&mut self, volts: f32) {
|
||||||
self.pid.target = volts*board::AV_ADC_GAIN;
|
self.target = 0.0;
|
||||||
|
self.pid.set_target(volts);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ready(&self) -> bool {
|
fn ready(&self) -> bool {
|
||||||
self.pid.is_within(1.0*board::AV_ADC_GAIN)
|
match self.last_av {
|
||||||
|
None => false,
|
||||||
|
Some(last_av) => (last_av - self.target).abs() < 1.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.pid.reset();
|
self.pid.reset();
|
||||||
|
board::set_hv_pwm(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_status(&self) -> ControllerStatus {
|
||||||
|
ControllerStatus {
|
||||||
|
ready: self.ready(),
|
||||||
|
av: self.last_av
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControllerStatus {
|
||||||
pub fn debug_print(&self) {
|
pub fn debug_print(&self) {
|
||||||
println!("anode ready: {}", self.ready());
|
println!("anode ready: {}", self.ready);
|
||||||
if self.pid.last_input.is_some() {
|
if self.av.is_some() {
|
||||||
println!("voltage: {}V", self.pid.last_input.unwrap()/board::AV_ADC_GAIN);
|
println!("voltage: {}V", self.av.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,12 +27,20 @@ pub struct Controller {
|
||||||
last_fbv: Option<f32>
|
last_fbv: Option<f32>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct ControllerStatus {
|
||||||
|
pub ready: bool,
|
||||||
|
pub fbi: Option<f32>,
|
||||||
|
pub fv: Option<f32>,
|
||||||
|
pub fbv: Option<f32>
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
pub const fn new() -> Controller {
|
pub const fn new() -> Controller {
|
||||||
Controller {
|
Controller {
|
||||||
fbi_target: 0.0,
|
fbi_target: 0.0,
|
||||||
fbi_range: board::EmissionRange::Low,
|
fbi_range: board::EmissionRange::Med,
|
||||||
fbi_buffer: [0.0; 16],
|
fbi_buffer: [0.0; 16],
|
||||||
fbi_buffer_count: 0,
|
fbi_buffer_count: 0,
|
||||||
last_fbi: None,
|
last_fbi: None,
|
||||||
|
@ -91,25 +99,20 @@ impl Controller {
|
||||||
board::set_fbv_pwm((volts/board::FBV_PWM_GAIN) as u16);
|
board::set_fbv_pwm((volts/board::FBV_PWM_GAIN) as u16);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn emission_ready(&self) -> bool {
|
fn emission_ready(&self) -> bool {
|
||||||
let tolerance = if self.fbi_range == board::EmissionRange::High { 0.20 } else { 0.02 };
|
|
||||||
match self.last_fbi {
|
match self.last_fbi {
|
||||||
None => false,
|
None => false,
|
||||||
Some(last_fbi) => (self.fbi_target - last_fbi).abs()/self.fbi_target < tolerance
|
Some(last_fbi) => (self.fbi_target - last_fbi).abs()/self.fbi_target < 0.02
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn bias_ready(&self) -> bool {
|
fn bias_ready(&self) -> bool {
|
||||||
match self.last_fbv {
|
match self.last_fbv {
|
||||||
None => false,
|
None => false,
|
||||||
Some(last_fbv) => (self.fbv_target - last_fbv).abs() < 1.0
|
Some(last_fbv) => (self.fbv_target - last_fbv).abs() < 1.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ready(&self) -> bool {
|
|
||||||
self.emission_ready() & self.bias_ready()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.pid.reset();
|
self.pid.reset();
|
||||||
self.fbi_buffer_count = 0;
|
self.fbi_buffer_count = 0;
|
||||||
|
@ -118,16 +121,28 @@ impl Controller {
|
||||||
self.last_fbv = None;
|
self.last_fbv = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_status(&self) -> ControllerStatus {
|
||||||
|
ControllerStatus {
|
||||||
|
ready: self.emission_ready() & self.bias_ready(),
|
||||||
|
fbi: self.last_fbi,
|
||||||
|
fv: self.last_fv,
|
||||||
|
fbv: self.last_fbv
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ControllerStatus {
|
||||||
pub fn debug_print(&self) {
|
pub fn debug_print(&self) {
|
||||||
println!("cathode ready: {}", self.ready());
|
println!("cathode ready: {}", self.ready);
|
||||||
if self.last_fbi.is_some() {
|
if self.fbi.is_some() {
|
||||||
println!("emission: {}mA", 1000.0*self.last_fbi.unwrap());
|
println!("emission: {}mA", 1000.0*self.fbi.unwrap());
|
||||||
}
|
}
|
||||||
if self.last_fv.is_some() {
|
if self.fv.is_some() {
|
||||||
println!("fil voltage: {}V", self.last_fv.unwrap());
|
println!("fil voltage: {}V", self.fv.unwrap());
|
||||||
}
|
}
|
||||||
if self.last_fbv.is_some() {
|
if self.fbv.is_some() {
|
||||||
println!("bias voltage: {}V", self.last_fbv.unwrap());
|
println!("bias voltage: {}V", self.fbv.unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ mod board;
|
||||||
mod pid;
|
mod pid;
|
||||||
mod loop_anode;
|
mod loop_anode;
|
||||||
mod loop_cathode;
|
mod loop_cathode;
|
||||||
|
mod electrometer;
|
||||||
|
|
||||||
static TIME: Mutex<Cell<u64>> = Mutex::new(Cell::new(0));
|
static TIME: Mutex<Cell<u64>> = Mutex::new(Cell::new(0));
|
||||||
|
|
||||||
|
@ -46,6 +47,9 @@ static LOOP_ANODE: Mutex<RefCell<loop_anode::Controller>> = Mutex::new(RefCell::
|
||||||
static LOOP_CATHODE: Mutex<RefCell<loop_cathode::Controller>> = Mutex::new(RefCell::new(
|
static LOOP_CATHODE: Mutex<RefCell<loop_cathode::Controller>> = Mutex::new(RefCell::new(
|
||||||
loop_cathode::Controller::new()));
|
loop_cathode::Controller::new()));
|
||||||
|
|
||||||
|
static ELECTROMETER: Mutex<RefCell<electrometer::Electrometer>> = Mutex::new(RefCell::new(
|
||||||
|
electrometer::Electrometer::new()));
|
||||||
|
|
||||||
|
|
||||||
pub struct UART0;
|
pub struct UART0;
|
||||||
|
|
||||||
|
@ -80,7 +84,7 @@ fn main() {
|
||||||
loop_anode.set_target(anode_cathode+cathode_bias);
|
loop_anode.set_target(anode_cathode+cathode_bias);
|
||||||
loop_cathode.set_emission_target(anode_cathode/10000.0);
|
loop_cathode.set_emission_target(anode_cathode/10000.0);
|
||||||
loop_cathode.set_bias_target(cathode_bias);
|
loop_cathode.set_bias_target(cathode_bias);
|
||||||
board::set_fv_pwm(10);
|
//board::set_fv_pwm(10);
|
||||||
});
|
});
|
||||||
|
|
||||||
println!(r#"
|
println!(r#"
|
||||||
|
@ -126,12 +130,12 @@ Ready."#);
|
||||||
Some(t) => if time > t {
|
Some(t) => if time > t {
|
||||||
latch_reset_time = None;
|
latch_reset_time = None;
|
||||||
cortex_m::interrupt::free(|cs| {
|
cortex_m::interrupt::free(|cs| {
|
||||||
board::reset_error();
|
|
||||||
// reset PID loops as they have accumulated large errors
|
// reset PID loops as they have accumulated large errors
|
||||||
// while the protection was active, which would cause
|
// while the protection was active, which would cause
|
||||||
// unnecessary overshoots.
|
// unnecessary overshoots.
|
||||||
LOOP_ANODE.borrow(cs).borrow_mut().reset();
|
LOOP_ANODE.borrow(cs).borrow_mut().reset();
|
||||||
LOOP_CATHODE.borrow(cs).borrow_mut().reset();
|
LOOP_CATHODE.borrow(cs).borrow_mut().reset();
|
||||||
|
board::reset_error();
|
||||||
});
|
});
|
||||||
println!("Protection reset");
|
println!("Protection reset");
|
||||||
}
|
}
|
||||||
|
@ -158,16 +162,18 @@ extern fn adc0_ss0(_ctxt: ADC0SS0) {
|
||||||
|
|
||||||
let mut loop_anode = LOOP_ANODE.borrow(cs).borrow_mut();
|
let mut loop_anode = LOOP_ANODE.borrow(cs).borrow_mut();
|
||||||
let mut loop_cathode = LOOP_CATHODE.borrow(cs).borrow_mut();
|
let mut loop_cathode = LOOP_CATHODE.borrow(cs).borrow_mut();
|
||||||
|
let mut electrometer = ELECTROMETER.borrow(cs).borrow_mut();
|
||||||
loop_anode.adc_input(av_sample);
|
loop_anode.adc_input(av_sample);
|
||||||
loop_cathode.adc_input(fbi_sample, fd_sample, fv_sample, fbv_sample);
|
loop_cathode.adc_input(fbi_sample, fd_sample, fv_sample, fbv_sample);
|
||||||
|
electrometer.adc_input(ic_sample);
|
||||||
|
|
||||||
let time = TIME.borrow(cs);
|
let time = TIME.borrow(cs);
|
||||||
time.set(time.get() + 1);
|
time.set(time.get() + 1);
|
||||||
|
|
||||||
if time.get() % 300 == 0 {
|
if time.get() % 300 == 0 {
|
||||||
loop_anode.debug_print();
|
//loop_anode.get_status().debug_print();
|
||||||
loop_cathode.debug_print();
|
//loop_cathode.get_status().debug_print();
|
||||||
println!("{}", ic_sample);
|
electrometer.get_status().debug_print();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
use core::num::Float;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Parameters {
|
pub struct Parameters {
|
||||||
pub kp: f32,
|
pub kp: f32,
|
||||||
|
@ -13,9 +11,9 @@ pub struct Parameters {
|
||||||
|
|
||||||
pub struct Controller {
|
pub struct Controller {
|
||||||
parameters: Parameters,
|
parameters: Parameters,
|
||||||
pub target: f32,
|
target: f32,
|
||||||
integral: f32,
|
integral: f32,
|
||||||
pub last_input: Option<f32>
|
last_input: Option<f32>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Controller {
|
impl Controller {
|
||||||
|
@ -58,12 +56,8 @@ impl Controller {
|
||||||
output
|
output
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
pub fn set_target(&mut self, target: f32) {
|
||||||
pub fn is_within(&self, tolerance: f32) -> bool {
|
self.target = target;
|
||||||
match self.last_input {
|
|
||||||
None => false,
|
|
||||||
Some(last_input) => (last_input - self.target).abs() < tolerance
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|
Loading…
Reference in New Issue