Compare commits

...

5 Commits

Author SHA1 Message Date
linuswck ec5bf1d6b6 gui, ld: term_status -> term_50ohm
- term_status (Is50Ohm / Not50Ohm) -> term_50ohm (On/Off)
2024-07-19 18:10:38 +08:00
linuswck 3737c2ed59 gui: increase the connection timeout value
- 0.1s is too short. Min settable sampling rate is 1.25Sps
2024-07-19 18:10:38 +08:00
linuswck 4cd328d98c ld: apply pwr limit setting to analog watchdog
- This fixes a bug which power limit is not applied to pd_mon at startup
2024-07-19 18:10:38 +08:00
linuswck af95de0f16 thermostat: cal gain error at TEC DAC output 2024-07-19 18:10:12 +08:00
linuswck db2f76771a Update Kirdy Driver Example Code 2024-07-19 18:10:08 +08:00
7 changed files with 178 additions and 64 deletions

View File

@ -1,45 +1,148 @@
from driver.kirdy_async import Kirdy
from pprint import pp
from driver.kirdy_async import Kirdy, FilterConfig
import asyncio
import signal
"""
Enter Device Firmware Upgrade(Dfu) mode
Please see README.md for flash instructions.
"""
async def enter_dfu_mode(kirdy: Kirdy):
print(await kirdy.device.dfu())
await kirdy.end_session()
exit()
"""
Configure Kirdy to actively report status
Press Ctrl + C to exit active report mode
"""
async def active_report(kirdy: Kirdy):
class SignalHandler:
def __init__(self):
signal.signal(signal.SIGINT, self.exit_gracefully)
signal.signal(signal.SIGTERM, self.exit_gracefully)
def exit_gracefully(self, signum, frame):
kirdy.stop_report_mode()
signal_handler = SignalHandler()
async for data in kirdy.report_mode():
pp(data)
"""
Configure Kirdy network and board specific transconductance settings.
These configs are saved to flash immediately after command is processed.
"""
async def device_cfg(kirdy: Kirdy):
# Kirdy rev0_3's gain and transconductance varies between boards to maximize the
# PD current range resolution.
await kirdy.device.set_pd_mon_fin_gain(1.0)
await kirdy.device.set_pd_mon_transconductance(1/1000.0)
# Network Settings will be updated on next reboot.
await kirdy.device.set_ip_settings(
addr="192.168.1.128",
port=1337,
prefix_len=24,
gateway="192.168.1.1"
)
# Hard reset Kirdy.
await kirdy.device.hard_reset()
await kirdy.end_session()
exit()
"""
Control and config laser diode and thermostat parameters.
"""
async def ld_thermostat_cfg(kirdy: Kirdy):
# Load the laser diode and thermostat settings from flash
await kirdy.device.restore_settings_from_flash()
# Power off the laser diode and thermostat and clear alarm (if any)
await kirdy.laser.set_power_on(False)
await kirdy.laser.clear_alarm()
await kirdy.thermostat.set_power_on(False)
await kirdy.thermostat.clear_alarm()
# Set the laser diode terminals not to be shorted
await kirdy.laser.set_ld_terms_short(False)
# Do not power up the laser & thermostat during initial startup
await kirdy.laser.set_default_pwr_on(False)
await kirdy.thermostat.set_default_pwr_on(False)
# The laser diode output current range is bounded by this software limit setting
await kirdy.laser.set_i(0)
await kirdy.laser.set_i_soft_limit(30.0 / 1000)
# Configure the laser diode output power limit and photodiode parameters
# Exceeding the measured power limit triggers overpower protection alarm.
# The laser diode power will be turned off while the thermostat power remains unchanged.
await kirdy.laser.set_ld_pwr_limit(0.0)
await kirdy.laser.set_pd_mon_dark_current(0.0)
await kirdy.laser.set_pd_mon_responsitivity(0.0)
# Configure the thermostat NTC thermistor parameters.
await kirdy.thermostat.set_sh_r0(10.0 * 1000)
await kirdy.thermostat.set_sh_t0(25)
await kirdy.thermostat.set_sh_beta(3900)
# Configure the thermostat output limits.
# The actual output current is limited by the hardware limit set below.
await kirdy.thermostat.set_tec_max_cooling_i(1.0)
await kirdy.thermostat.set_tec_max_heating_i(1.0)
await kirdy.thermostat.set_tec_max_v(4.0)
# Configure the thermostat temperature monitor limit.
# Exceeding the limit will trigger over temperature protection alarm.
# The laser diode and thermostat power will be turned off.
await kirdy.thermostat.set_temp_mon_upper_limit(70)
await kirdy.thermostat.set_temp_mon_lower_limit(0)
# Configure the thermostat PID related parameter.
# You can configure the PID parameter with the autotune tool.
# Here provides an example if it is configured manually.
await kirdy.thermostat.set_temperature_setpoint(25)
await kirdy.thermostat.set_pid_kp(0.15668282198105507)
await kirdy.thermostat.set_pid_ki(0.002135962407793784)
await kirdy.thermostat.set_pid_kd(0.829254515277143)
await kirdy.thermostat.set_pid_output_max(1.0)
await kirdy.thermostat.set_pid_output_min(-1.0)
# Configure the thermostat ADC Filter Setting / PID Update Rate / Report Rate.
# The ADC sampling rate determines the report and pid update rate.
# The chosen filter and sampling rate affects the noise of the readings.
# For details, please refer to the AD7172 datasheet.
await kirdy.thermostat.config_temp_adc_filter(FilterConfig.Sinc5Sinc1With50hz60HzRejection.f16sps)
# Configure thermostat to run in PID control mode
await kirdy.thermostat.set_pid_control_mode()
# When control mode is switched from PID to constant current(CC) control mode,
# the thermostat keeps its instantaneous output current unchanged.
# Thermostat output current should only be set if it is in CC control mode
# or the value set will not be overwritten by PID output.
await kirdy.thermostat.set_constant_current_control_mode()
await kirdy.thermostat.set_tec_i_out(0.0)
# Save the above settings configured into the flash
await kirdy.device.save_current_settings_to_flash()
# Power on the laser diode and thermostat
await kirdy.laser.set_power_on(True)
await kirdy.thermostat.set_power_on(True)
pp(await kirdy.device.get_settings_summary())
pp(await kirdy.device.get_status_report())
async def main():
kirdy = Kirdy()
await kirdy.start_session(host='192.168.1.128', port=1337, timeout=0.25)
await kirdy.device.set_active_report_mode(False)
await kirdy.laser.set_power_on(False)
await kirdy.laser.clear_alarm()
await kirdy.laser.set_i(0)
await kirdy.laser.set_i_soft_limit(0.25)
await kirdy.laser.set_power_on(True)
await kirdy.thermostat.set_power_on(False)
await kirdy.thermostat.clear_alarm()
await kirdy.thermostat.set_sh_r0(10.0*1000)
await kirdy.thermostat.set_sh_t0(25)
await kirdy.thermostat.set_sh_beta(3900)
await kirdy.thermostat.set_temperature_setpoint(25)
await kirdy.thermostat.set_temp_mon_upper_limit(40)
await kirdy.thermostat.set_temp_mon_lower_limit(10)
await kirdy.thermostat.set_pid_kp(0.15668282198105507)
await kirdy.thermostat.set_pid_ki(0.002135962407793784)
await kirdy.thermostat.set_pid_kd(0.829254515277143)
await kirdy.thermostat.set_pid_output_max(1.0)
await kirdy.thermostat.set_pid_output_min(-1.0)
await kirdy.thermostat.config_temp_adc_filter("Sinc5Sinc1With50hz60HzRejection", "F16SPS")
await kirdy.thermostat.set_power_on(True)
await kirdy.thermostat.set_pid_control_mode()
await kirdy.laser.set_default_pwr_on(False)
await kirdy.thermostat.set_default_pwr_on(True)
await kirdy.device.save_current_settings_to_flash()
async for data in kirdy.report_mode():
print(data)
# await ld_thermostat_cfg(kirdy)
# await active_report(kirdy)
# await device_cfg(kirdy)
await enter_dfu_mode(kirdy)
await kirdy.end_session()

View File

@ -129,16 +129,11 @@ class FilterConfig:
def _filter_type(self):
return "Sinc3"
############## Rewrite
class Sinc3WithFineODR():
def __init__(self, rate):
assert rate >= 1.907465 and rate <= 31250
self.rate = float(rate)
# def rate(self, rate):
# assert rate >= 1.907465 and rate <= 31250
# return float(rate)
def _odr_type(self):
return "sinc3fineodr"
@ -228,7 +223,7 @@ class Device:
'ld_i_set': 0.0, # Laser Diode Output Current (A)
'pd_i': 2.0000002e-06, # Internal Photodiode Monitor current (A)
'pd_pwr': None, # Power Readings from Internal Photodiode (W). Return None if pd_mon parameter(s) are not defined.
'term_status': 'Is50Ohm' # Is the Low Frequency Modulation Input's Impedance 50 Ohm? (Is50Ohm/Not50Ohm)
'term_50ohm': 'Is50Ohm' # Is the Low Frequency Modulation Input's Impedance 50 Ohm? (On/Off)
},
'thermostat': {
'pwr_on': False, # Tec Power is On (True/False)

View File

@ -332,7 +332,7 @@ class MainWindow(QtWidgets.QMainWindow):
{'name': 'LD Current Set', 'type': 'float', 'suffix': 'A', 'siPrefix': True, 'readonly': True},
{'name': 'PD Current', 'type': 'float', 'suffix': 'A', 'siPrefix': True, 'readonly': True},
{'name': 'PD Power', 'type': 'float', 'suffix': 'W', 'siPrefix': True, 'readonly': True},
{'name': 'LF Mod Impedance', 'type': 'list', 'limits': ['Is50Ohm', 'Not50Ohm'], 'readonly': True}
{'name': 'LF Mod Termination (50 Ohm)', 'type': 'list', 'limits': ['On', 'Off'], 'readonly': True}
]},
{'name': 'Output Config', 'expanded': True, 'type': 'group', 'children': [
{'name': 'LD Current Set', 'type': 'float', 'value': 0, 'step': 1, 'decimals': 6, 'limits': (0, 1),
@ -818,7 +818,7 @@ class MainWindow(QtWidgets.QMainWindow):
self.params[1].child('Readings', 'PD Power').setValue(report["pd_pwr"])
else:
self.params[1].child('Readings', 'PD Power').setValue(0)
self.params[1].child('Readings', 'LF Mod Impedance').setValue(report["term_status"])
self.params[1].child('Readings', 'LF Mod Termination (50 Ohm)').setValue(report["term_50ohm"])
except Exception as e:
logging.error(f"Params tree cannot be updated. Data:{report}", exc_info=True)
@ -898,7 +898,7 @@ class MainWindow(QtWidgets.QMainWindow):
try:
if not (self.kirdy.connecting() or self.kirdy.connected()):
self.status_lbl.setText("Connecting...")
await self.kirdy.start_session(host=host, port=port, timeout=0.1)
await self.kirdy.start_session(host=host, port=port, timeout=5.0)
await self._on_connection_changed(True)
else:
await self.bail()

View File

@ -10,7 +10,7 @@ use uom::si::{electric_current::{ampere, milliampere},
power::milliwatt};
use crate::{device::sys_timer::sleep,
laser_diode::{ld_ctrl::{Impedance, LdCtrl},
laser_diode::{ld_ctrl::{Impedance50Ohm, LdCtrl},
ld_current_out_ctrl_timer::LdCurrentOutCtrlTimer,
ld_pwr_exc_protector::{self, LdPwrExcProtector},
pd_mon_params}};
@ -66,7 +66,7 @@ pub struct StatusReport {
ld_i_set: ElectricCurrent,
pd_i: ElectricCurrent,
pd_pwr: Power,
term_status: Impedance,
term_50ohm: Impedance50Ohm,
}
pub struct LdDrive {
@ -210,7 +210,7 @@ impl LdDrive {
self.settings.default_pwr_on = pwr_on;
}
pub fn get_term_status(&mut self) -> Impedance {
pub fn get_term_status(&mut self) -> Impedance50Ohm {
self.ctrl.get_lf_mod_in_impedance()
}
@ -226,7 +226,7 @@ impl LdDrive {
ld_i_set: ld_i_set,
pd_i: self.get_pd_i(),
pd_pwr: self.get_pd_pwr(),
term_status: self.get_term_status(),
term_50ohm: self.get_term_status(),
}
}
@ -255,6 +255,9 @@ impl LdDrive {
self.settings.pd_mon_params = settings.pd_mon_params;
self.settings.ld_pwr_limit = settings.ld_pwr_limit;
self.settings.default_pwr_on = settings.default_pwr_on;
self.set_ld_power_limit(settings.ld_pwr_limit);
if self.settings.ld_terms_short {
self.ld_short();
} else {

View File

@ -11,9 +11,9 @@ use uom::si::{electric_current::ampere,
use crate::laser_diode::max5719::{self, Dac};
#[derive(Deserialize, Serialize, Debug, Clone, Copy)]
pub enum Impedance {
Is50Ohm,
Not50Ohm,
pub enum Impedance50Ohm {
On,
Off,
}
pub trait ChannelPins {
@ -66,11 +66,11 @@ impl LdCtrl {
self.phy.current_source_short_pin.set_high();
}
pub fn get_lf_mod_in_impedance(&mut self) -> Impedance {
pub fn get_lf_mod_in_impedance(&mut self) -> Impedance50Ohm {
if self.phy.termination_status_pin.is_high() {
Impedance::Is50Ohm
Impedance50Ohm::On
} else {
Impedance::Not50Ohm
Impedance50Ohm::Off
}
}

View File

@ -12,7 +12,7 @@ use stm32f4xx_hal::{adc::{config::{self, AdcConfig},
timer::pwm::PwmChannel};
use uom::si::{electric_potential::millivolt, f32::ElectricPotential, ratio::ratio};
use crate::thermostat::ad5680;
use crate::{device::sys_timer::sleep, thermostat::ad5680};
pub const PWM_FREQ_KHZ: KilohertzU32 = KilohertzU32::from_raw(20);
@ -77,6 +77,7 @@ pub struct MAX1968 {
pub phy: MAX1968Phy<Channel0>,
pub pins_adc: Adc<ADC1>,
pub dma_adc: DMA_Transfer<Stream2<DMA2>, 1, Adc<ADC2>, PeripheralToMemory, &'static mut [u16; 16]>,
pub dac_out_range: ElectricPotential,
prev_vtec_volt: ElectricPotential,
prev_itec_volt: ElectricPotential,
}
@ -115,7 +116,7 @@ static mut ADC2_FIRST_BUFFER: [u16; 16] = [0; 16];
static mut ADC2_LOCAL_BUFFER: [u16; 16] = [0; 16];
impl MAX1968 {
pub fn new(phy_ch0: MAX1968Phy<Channel0>, adc1: ADC1, adc2: ADC2, dma2: DMA2) -> Self {
pub fn new(mut phy_ch0: MAX1968Phy<Channel0>, adc1: ADC1, adc2: ADC2, dma2: DMA2) -> Self {
let adc_config = AdcConfig::default()
.clock(config::Clock::Pclk2_div_8)
.default_sample_time(config::SampleTime::Cycles_480);
@ -222,10 +223,26 @@ impl MAX1968 {
NVIC::unmask(interrupt::DMA2_STREAM2);
}
phy_ch0.dac.set(ad5680::MAX_VALUE).unwrap();
sleep(500);
let mut sample = 0;
for _ in 0..512 {
sample += pins_adc1.convert(
&phy_ch0.dac_feedback_pin,
stm32f4xx_hal::adc::config::SampleTime::Cycles_480,
) as u32;
}
let sample = sample / 512 as u32;
let mv = pins_adc1.sample_to_millivolts(sample as u16);
let dac_out_range = ElectricPotential::new::<millivolt>(mv as f32);
phy_ch0.dac.set(0).unwrap();
MAX1968 {
phy: phy_ch0,
pins_adc: pins_adc1,
dma_adc: dma_adc,
dac_out_range: dac_out_range,
prev_vtec_volt: ElectricPotential::new::<millivolt>(0.0),
prev_itec_volt: ElectricPotential::new::<millivolt>(0.0),
}

View File

@ -47,11 +47,6 @@ pub struct TecSettings {
}
impl TecSettings {
pub const DAC_OUT_V_MAX: ElectricPotential = ElectricPotential {
dimension: PhantomData,
units: PhantomData,
value: 3.0,
};
pub const TEC_VSEC_BIAS_V: ElectricPotential = ElectricPotential {
dimension: PhantomData,
units: PhantomData,
@ -237,7 +232,8 @@ impl Thermostat {
pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent {
let voltage = i_tec * 10.0 * R_SENSE + self.tec_settings.center_pt;
let voltage = self.max1968.set_dac(voltage, TecSettings::DAC_OUT_V_MAX);
let voltage = self.max1968.set_dac(voltage, self.max1968.dac_out_range);
self.tec_settings.i_set = (voltage - self.tec_settings.center_pt) / (10.0 * R_SENSE);
self.tec_settings.i_set
}
@ -335,7 +331,7 @@ impl Thermostat {
best_error = error;
start_value = prev_value;
let vref = (value as f32 / ad5680::MAX_VALUE as f32) * TecSettings::DAC_OUT_V_MAX;
let vref = (value as f32 / ad5680::MAX_VALUE as f32) * self.max1968.dac_out_range;
self.set_center_pt(vref);
}
prev_value = value;