add electrometer, introduce *Status objects

This commit is contained in:
Sebastien Bourdeauducq 2017-05-11 23:15:01 +08:00
parent c676102b33
commit 44d95973ca
6 changed files with 169 additions and 45 deletions

View File

@ -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);

View File

@ -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());
}
}
}

View File

@ -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());
} }
} }
} }

View File

@ -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());
} }
} }
} }

View File

@ -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();
} }
}); });
} }

View File

@ -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)]