From 475fe28604a8004c8046c90d3de66a71cc64b6ec Mon Sep 17 00:00:00 2001 From: linuswck Date: Wed, 20 Dec 2023 12:08:48 +0800 Subject: [PATCH] Initial Commit for AD5680 and MAX1968 Drivers - GPIO Initializations for AD5680, MAX1968 drivers - CTLI voltage of MAX1968 can be set with AD5680 - All features of MAX1968 can be controlled --- Cargo.lock | 46 +++++- Cargo.toml | 1 + src/device/boot.rs | 8 +- src/device/gpio.rs | 44 +++++- src/main.rs | 9 +- src/thermostat/ad5680.rs | 45 ++++++ src/thermostat/max1968.rs | 284 +++++++++++++++++++++++++++++++++++ src/thermostat/mod.rs | 2 + src/thermostat/thermostat.rs | 0 9 files changed, 426 insertions(+), 13 deletions(-) create mode 100644 src/thermostat/ad5680.rs create mode 100644 src/thermostat/max1968.rs create mode 100644 src/thermostat/thermostat.rs diff --git a/Cargo.lock b/Cargo.lock index de3cb1a..90bc528 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,6 +290,7 @@ dependencies = [ "smoltcp", "stm32-eth", "stm32f4xx-hal", + "uom", "usb-device", "usbd-serial", ] @@ -462,6 +463,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "serde" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.193" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.41", +] + [[package]] name = "smoltcp" version = "0.10.0" @@ -579,18 +600,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.51" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" dependencies = [ "proc-macro2", "quote", @@ -614,6 +635,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "ufmt-write" version = "0.1.0" @@ -626,6 +653,17 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "uom" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e76503e636584f1e10b9b3b9498538279561adcef5412927ba00c2b32c4ce5ed" +dependencies = [ + "num-traits", + "serde", + "typenum", +] + [[package]] name = "usb-device" version = "0.2.9" diff --git a/Cargo.toml b/Cargo.toml index 7b2d00f..6dc7339 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ cortex-m-log = { version = "0.7.0", features = ["log-integration", "semihosting" stm32f4xx-hal = { version = "0.14.0", features = ["rt", "stm32f407", "usb_fs"] } stm32-eth = { version = "0.5.2", features = ["stm32f407"] } smoltcp = { version = "0.10.0", default-features = false, features = ["proto-ipv4", "socket-tcp", "log", "medium-ethernet"] } +uom = { version = "0.30", default-features = false, features = ["autoconvert", "si", "f64", "use_serde"] } num-traits = { version = "0.2.15", default-features = false, features = ["libm"] } usb-device = "0.2.9" usbd-serial = "0.1.1" diff --git a/src/device/boot.rs b/src/device/boot.rs index 6133c01..c5b14c0 100644 --- a/src/device/boot.rs +++ b/src/device/boot.rs @@ -1,5 +1,5 @@ use super::{gpio, sys_timer, usb}; -use crate::laser_diode::current_sources::*; +use crate::{laser_diode::current_sources::*, thermostat::max1968}; use fugit::ExtU32; use log::info; use stm32f4xx_hal::{ @@ -31,13 +31,15 @@ pub fn bootup(mut core_perif: CorePeripherals, perif: Peripherals) -> Independen sys_timer::setup(core_perif.SYST, clocks); - let (_eth_pins, usb, current_source_phy) = gpio::setup( + let (_eth_pins, usb, current_source_phy, max1968_phy) = gpio::setup( clocks, + perif.TIM4, perif.GPIOA, perif.GPIOB, perif.GPIOC, perif.GPIOD, perif.GPIOG, + perif.SPI1, perif.SPI2, perif.OTG_FS_GLOBAL, perif.OTG_FS_DEVICE, @@ -56,6 +58,8 @@ pub fn bootup(mut core_perif: CorePeripherals, perif: Peripherals) -> Independen laser.setup(); laser.set_current(0.1).unwrap(); + let tec_driver = max1968::MAX1968::new(max1968_phy, perif.ADC1); + let mut wd = IndependentWatchdog::new(perif.IWDG); wd.start(WATCHDOG_PERIOD.millis()); wd.feed(); diff --git a/src/device/gpio.rs b/src/device/gpio.rs index 4fb560f..575d650 100644 --- a/src/device/gpio.rs +++ b/src/device/gpio.rs @@ -1,13 +1,19 @@ use crate::laser_diode::current_sources::*; +use crate::thermostat::ad5680; +use crate::thermostat::max1968::{MAX1968PinSet, PWM_FREQ_KHZ}; use fugit::RateExtU32; use stm32_eth::EthPins; use stm32f4xx_hal::{ gpio::{gpioa::*, gpiob::*, gpioc::*, gpiog::*, GpioExt, Input}, otg_fs::USB, - pac::{GPIOA, GPIOB, GPIOC, GPIOD, GPIOG, OTG_FS_DEVICE, OTG_FS_GLOBAL, OTG_FS_PWRCLK, SPI2}, + pac::{ + GPIOA, GPIOB, GPIOC, GPIOD, GPIOG, OTG_FS_DEVICE, OTG_FS_GLOBAL, OTG_FS_PWRCLK, SPI1, SPI2, + TIM4, + }, rcc::Clocks, spi, spi::{NoMiso, Spi}, + timer::pwm::PwmExt, }; pub type EthernetPins = @@ -15,11 +21,13 @@ pub type EthernetPins = pub fn setup( clocks: Clocks, + tim4: TIM4, gpioa: GPIOA, gpiob: GPIOB, gpioc: GPIOC, gpiod: GPIOD, gpiog: GPIOG, + spi1: SPI1, spi2: SPI2, otg_fs_global: OTG_FS_GLOBAL, otg_fs_device: OTG_FS_DEVICE, @@ -28,6 +36,7 @@ pub fn setup( EthernetPins, USB, CurrentSourcePhyConstruct, + MAX1968PinSet, // photo_diode_phy, // thermostat_phy ) { @@ -77,5 +86,36 @@ pub fn setup( current_source_short: gpioa.pa4.into_push_pull_output(), }; - (eth_pins, usb, current_source_phy) + let pwm_chs = ( + gpiob.pb6.into_alternate(), + gpiob.pb7.into_alternate(), + gpiob.pb8.into_alternate(), + ); + let (max_i_neg0, max_v0, max_i_pos0) = + tim4.pwm_hz(pwm_chs, PWM_FREQ_KHZ.kHz(), &clocks).split(); + + let max1968_phy = MAX1968PinSet { + dac_spi: Spi::new( + spi1, + ( + gpiob.pb3.into_alternate(), + NoMiso {}, + gpiob.pb5.into_alternate(), + ), + ad5680::SPI_MODE, + ad5680::SPI_CLOCK_MHZ.MHz(), + &clocks, + ), + dac_sync: gpiob.pb4.into_push_pull_output(), + dac_vfb: gpioc.pc0.into_analog(), + shdn: gpioa.pa5.into_push_pull_output(), + vref: gpioa.pa6.into_analog(), + vtec: gpiob.pb0.into_analog(), + itec: gpiob.pb1.into_analog(), + max_v0: max_v0, + max_i_pos0: max_i_pos0, + max_i_neg0: max_i_neg0, + }; + + (eth_pins, usb, current_source_phy, max1968_phy) } diff --git a/src/main.rs b/src/main.rs index a628e7d..fa74079 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,14 +7,14 @@ use stm32f4xx_hal::pac::{CorePeripherals, Peripherals}; mod device; mod laser_diode; +mod thermostat; use device::{boot::bootup, log_setup, sys_timer}; +use crate::thermostat::max1968; + // If RTT is used, print panic info through RTT #[cfg(all(feature = "RTT", not(test)))] -use { - core::panic::PanicInfo, - rtt_target::rprintln, -}; +use {core::panic::PanicInfo, rtt_target::rprintln}; #[cfg(all(feature = "RTT", not(test)))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { @@ -28,7 +28,6 @@ use panic_halt as _; #[cfg(not(test))] #[entry] fn main() -> ! { - log_setup::init_log(); info!("Kirdy init"); diff --git a/src/thermostat/ad5680.rs b/src/thermostat/ad5680.rs new file mode 100644 index 0000000..6afca8c --- /dev/null +++ b/src/thermostat/ad5680.rs @@ -0,0 +1,45 @@ +use crate::device::sys_timer::sleep; +use stm32f4xx_hal::{ + hal::{blocking::spi::Transfer, digital::v2::OutputPin}, + spi, +}; + +/// SPI Mode 1 +pub const SPI_MODE: spi::Mode = spi::Mode { + polarity: spi::Polarity::IdleLow, + phase: spi::Phase::CaptureOnSecondTransition, +}; +pub const SPI_CLOCK_MHZ: u32 = 30; + +pub const MAX_VALUE: u32 = 0x3FFFF; + +pub struct Dac, S: OutputPin> { + spi: SPI, + sync: S, +} + +impl, S: OutputPin> Dac { + pub fn new(spi: SPI, mut sync: S) -> Self { + let _ = sync.set_low(); + + Dac { spi, sync } + } + + fn write(&mut self, buf: &mut [u8]) -> Result<(), SPI::Error> { + // pulse sync to start a new transfer. leave sync idle low + // afterwards to save power as recommended per datasheet. + let _ = self.sync.set_high(); + // must be high for >= 33 ns + sleep(1); + let _ = self.sync.set_low(); + self.spi.transfer(buf)?; + Ok(()) + } + + pub fn set(&mut self, value: u32) -> Result { + let value = value.min(MAX_VALUE); + let mut buf = [(value >> 14) as u8, (value >> 6) as u8, (value << 2) as u8]; + self.write(&mut buf)?; + Ok(value) + } +} diff --git a/src/thermostat/max1968.rs b/src/thermostat/max1968.rs new file mode 100644 index 0000000..7db78c6 --- /dev/null +++ b/src/thermostat/max1968.rs @@ -0,0 +1,284 @@ +use core::u16; + +use crate::thermostat::ad5680; + +use fugit::RateExtU32; +use stm32f4xx_hal::{ + adc::{ + config::{self, AdcConfig}, + Adc, + }, + gpio::{gpioa::*, gpiob::*, gpioc::*, Alternate, Analog, Output, PushPull}, + hal, + pac::{ADC1, SPI1, TIM4}, + rcc::Clocks, + spi::{NoMiso, Spi, TransferModeNormal}, + timer::pwm::{PwmChannel, PwmExt}, +}; + +use uom::si::{ + electric_current::ampere, + electric_potential::{millivolt, volt}, + electrical_resistance::ohm, + f64::{ElectricCurrent, ElectricPotential, ElectricalResistance}, + ratio::ratio, +}; + +pub const PWM_FREQ_KHZ: u32 = 20; +pub const R_SENSE: f64 = 0.05; + +// Rev 0_2: DAC Chip connects 3V3 reference voltage and thus provide 0-3.3V output range +// TODO: Rev 0_3: DAC Chip connects 3V3 reference voltage, +// which is then passed through a resistor divider to provide 0-3V output range +const DAC_OUT_V_MAX: f64 = 3.3; +const TEC_VSEC_BIAS_V: f64 = 1.65; +const MAX_V_DUTY_MAX: f64 = 1.5 / 3.3; +const MAX_I_POS_DUTY_MAX: f64 = 1.5 / 3.3; +const MAX_I_NEG_DUTY_MAX: f64 = 1.5 / 3.3; + +pub struct MAX1968PinSet { + pub dac_spi: DacSpi, + pub dac_sync: DacSync, + pub shdn: PA5>, + pub dac_vfb: PC0, + pub vref: PA6, + pub vtec: PB0, + pub itec: PB1, + pub max_v0: PwmChannel, + pub max_i_pos0: PwmChannel, + pub max_i_neg0: PwmChannel, +} + +type DacSpi = Spi>, NoMiso, PB5>), TransferModeNormal>; +type DacSync = PB4>; +pub struct MaxAdcPins { + pub dac_vfb: PC0, + pub vref: PA6, + pub itec: PB1, + pub vtec: PB0, +} + +pub struct MAX1968 { + pub center_pt: ElectricPotential, // To be moved to a miniconf crate's struct + pub adc: Adc, + pub dac: ad5680::Dac, + pub shdn: PA5>, + pub adc_pins: MaxAdcPins, + pub pwm_pins: PwmPins, +} +pub struct PwmPins { + pub max_v0: PwmChannel, + pub max_i_pos0: PwmChannel, + pub max_i_neg0: PwmChannel, +} +enum PwmPinsEnum { + MaxV, + MaxPosI, + MaxNegI, +} + +pub enum AdcReadTarget { + VREF, + DacVfb, + ITec, + VTec, +} + +impl PwmPins { + fn setup(clocks: Clocks, tim4: TIM4, max_v0: PB7, max_i_pos0: PB8, max_i_neg0: PB6) -> PwmPins { + let freq = 20.kHz(); + + fn init_pwm_pin>(pin: &mut P) { + pin.set_duty(0); + pin.enable(); + } + + let channels = ( + max_i_neg0.into_alternate::<2>(), + max_v0.into_alternate::<2>(), + max_i_pos0.into_alternate::<2>(), + ); + + let (mut max_i_neg0, mut max_v0, mut max_i_pos0) = + tim4.pwm_hz(channels, freq, &clocks).split(); + + init_pwm_pin(&mut max_v0); + init_pwm_pin(&mut max_i_neg0); + init_pwm_pin(&mut max_i_pos0); + + PwmPins { + max_v0, + max_i_pos0, + max_i_neg0, + } + } +} + +impl MAX1968 { + pub fn new(pins: MAX1968PinSet, adc1: ADC1) -> Self { + let dac = ad5680::Dac::new(pins.dac_spi, pins.dac_sync); + + let config = AdcConfig::default() + .clock(config::Clock::Pclk2_div_2) + .default_sample_time(config::SampleTime::Cycles_480); + + let pins_adc = Adc::adc1(adc1, true, config); + + MAX1968 { + center_pt: ElectricPotential::new::(1500.0), + adc: pins_adc, + dac: dac, + shdn: pins.shdn, + adc_pins: MaxAdcPins { + dac_vfb: pins.dac_vfb, + vref: pins.vref, + itec: pins.itec, + vtec: pins.vtec, + }, + pwm_pins: PwmPins { + max_v0: pins.max_v0, + max_i_pos0: pins.max_i_pos0, + max_i_neg0: pins.max_i_neg0, + }, + } + } + + pub fn power_down(&mut self) { + let _ = self.shdn.set_high(); + } + + pub fn power_up(&mut self) { + let _ = self.shdn.set_high(); + } + + pub fn set_center_point(&mut self, value: ElectricPotential) { + self.center_pt = value; + } + + fn set_dac(&mut self, voltage: ElectricPotential) -> ElectricPotential { + let value = ((voltage / ElectricPotential::new::(DAC_OUT_V_MAX)).get::() + * (ad5680::MAX_VALUE as f64)) as u32; + self.dac.set(value).unwrap(); + // TODO: Store the set-ed DAC Voltage Value + voltage + } + + pub fn set_i(&mut self, i_tec: ElectricCurrent) -> ElectricCurrent { + let center_point = self.center_pt; + let r_sense = ElectricalResistance::new::(R_SENSE); + let voltage = i_tec * 10.0 * r_sense + center_point; + let voltage = self.set_dac(voltage); + let i_tec = (voltage - center_point) / (10.0 * r_sense); + i_tec + } + + // AN4073: ADC Reading Dispersion can be reduced through Averaging + // Upon test, 16 Point Averaging = +-3 LSB Dispersion + fn adc_read(&mut self, adc_read_target: AdcReadTarget, avg_pt: u16) -> ElectricPotential { + let mut sample: u32 = 0; + sample = match adc_read_target { + AdcReadTarget::VREF => { + for _ in (0..avg_pt).rev() { + sample += self.adc.convert( + &self.adc_pins.vref, + stm32f4xx_hal::adc::config::SampleTime::Cycles_480, + ) as u32; + } + sample / avg_pt as u32 + } + AdcReadTarget::DacVfb => { + for _ in (0..avg_pt).rev() { + sample += self.adc.convert( + &self.adc_pins.dac_vfb, + stm32f4xx_hal::adc::config::SampleTime::Cycles_480, + ) as u32; + } + sample / avg_pt as u32 + } + AdcReadTarget::ITec => { + for _ in (0..avg_pt).rev() { + sample += self.adc.convert( + &self.adc_pins.itec, + stm32f4xx_hal::adc::config::SampleTime::Cycles_480, + ) as u32; + } + sample / avg_pt as u32 + } + AdcReadTarget::VTec => { + for _ in (0..avg_pt).rev() { + sample += self.adc.convert( + &self.adc_pins.vtec, + stm32f4xx_hal::adc::config::SampleTime::Cycles_480, + ) as u32; + } + sample / avg_pt as u32 + } + }; + let mv = self.adc.sample_to_millivolts(sample as u16); + ElectricPotential::new::(mv as f64) + } + + pub fn get_vref(&mut self) -> ElectricPotential { + self.adc_read(AdcReadTarget::VREF, 16) + } + + pub fn get_dac_vfb(&mut self) -> ElectricPotential { + // Fixme: Rev0_2 does not have this feature + unimplemented!() + //self.adc_read(AdcReadTarget:: DacVfb, 1) + } + + pub fn get_tec_i(&mut self) -> ElectricCurrent { + (self.adc_read(AdcReadTarget::ITec, 1) - self.center_pt) + / ElectricalResistance::new::(0.4) + } + + pub fn get_tec_v(&mut self) -> ElectricPotential { + // Fixme: Rev0_2 has Analog Input Polarity Reversed + // Remove the -ve sign for Rev0_3 + -(self.adc_read(AdcReadTarget::VTec, 1) - ElectricPotential::new::(TEC_VSEC_BIAS_V)) + * 4.0 + } + + fn set_pwm(&mut self, pwm_pin: PwmPinsEnum, duty: f64, max_duty: f64) -> f64 { + fn set>(pin: &mut P, duty: f64) -> f64 { + let max = pin.get_max_duty(); + let value = ((duty * (max as f64)) as u16).min(max); + pin.set_duty(value); + pin.enable(); + value as f64 / (max as f64) + } + + let duty = duty.min(max_duty); + + match pwm_pin { + PwmPinsEnum::MaxV => set(&mut self.pwm_pins.max_v0, duty), + PwmPinsEnum::MaxPosI => set(&mut self.pwm_pins.max_i_pos0, duty), + PwmPinsEnum::MaxNegI => set(&mut self.pwm_pins.max_i_neg0, duty), + } + } + + pub fn set_max_v(&mut self, max_v: ElectricPotential) -> (ElectricPotential, ElectricPotential) { + let max = ElectricPotential::new::(6.0); + let v = max_v / 4.0; + let duty = (v / ElectricPotential::new::(3.3)).get::(); + let duty = self.set_pwm(PwmPinsEnum::MaxV, duty, MAX_V_DUTY_MAX); + (duty * max, max) + } + + pub fn set_max_i_pos(&mut self, max_i_pos: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { + let max = ElectricCurrent::new::(3.0); + let v = 10.0 * max_i_pos * ElectricalResistance::new::(R_SENSE); + let duty = (v / ElectricPotential::new::(3.3)).get::(); + let duty = self.set_pwm(PwmPinsEnum::MaxPosI, duty, MAX_I_POS_DUTY_MAX); + (duty * max * 3.3 / 1.5, max) + } + + pub fn set_max_i_neg(&mut self, max_i_neg: ElectricCurrent) -> (ElectricCurrent, ElectricCurrent) { + let max = ElectricCurrent::new::(3.0); + let v = 10.0 * max_i_neg * ElectricalResistance::new::(R_SENSE); + let duty = (v / ElectricPotential::new::(3.3)).get::(); + let duty = self.set_pwm(PwmPinsEnum::MaxNegI, duty, MAX_I_NEG_DUTY_MAX); + (duty * max * 3.3 / 1.5, max) + } +} diff --git a/src/thermostat/mod.rs b/src/thermostat/mod.rs index e69de29..5137637 100644 --- a/src/thermostat/mod.rs +++ b/src/thermostat/mod.rs @@ -0,0 +1,2 @@ +pub mod ad5680; +pub mod max1968; diff --git a/src/thermostat/thermostat.rs b/src/thermostat/thermostat.rs new file mode 100644 index 0000000..e69de29