diff --git a/docs/supply-monitor.md b/docs/supply-monitor.md new file mode 100644 index 0000000..f21c4ee --- /dev/null +++ b/docs/supply-monitor.md @@ -0,0 +1,21 @@ +# Supply Monitor + +## Measurement + +Measurement done at Coredump with `psu-rn` and `UT61E` multimeter. + +VDDA was measured to be 3.28V at Vbat = 4.51V + +|Vbat|VREF Raw|VDDA|Vin raw|Vin new|Vin old| +|----|--------|----|-------|-------|-------| +|5.00| 1580 |3.15| 3850 | 4.89 | 5.12 | +|4.51| 1580 |3.15| 3476 | 4.41 | 4.62 | +|4.01| 1580 |3.15| 3104 | 3.94 | 4.12 | +|3.50| 1580 |3.15| 2716 | 3.45 | 3.61 | +|3.30| 1580 |3.15| 2568 | 3.25 | 3.42 | +|3.00| 1736 |2.87| 2563 | 2.97 | 3.41 | +|2.60| 2006 |2.49| 2578 | 2.58 | 3.43 | + +The new methods seems to calculate VDDA slightly wrong and thus calculates a +too low value for Vin. But the error seems to be similar than with the old +measurement method. diff --git a/firmware/.embed.toml b/firmware/.embed.toml index 2b89e5e..09281e7 100644 --- a/firmware/.embed.toml +++ b/firmware/.embed.toml @@ -5,7 +5,8 @@ chip = "STM32L071KBTx" [default.probe] protocol = "Swd" -speed = 2500 +#speed = 2500 +#speed = 2000 # Disable everything for default command diff --git a/firmware/Cargo.lock b/firmware/Cargo.lock index 3ceb9b7..136e23c 100644 --- a/firmware/Cargo.lock +++ b/firmware/Cargo.lock @@ -226,6 +226,7 @@ version = "0.2.0" dependencies = [ "bitfield", "cortex-m", + "cortex-m-rt", "cortex-m-rtic", "embedded-hal", "embedded-time", diff --git a/firmware/Cargo.toml b/firmware/Cargo.toml index 0ba4b18..0c5db8c 100644 --- a/firmware/Cargo.toml +++ b/firmware/Cargo.toml @@ -17,6 +17,7 @@ gfroerli-common = { path = "../common" } bitfield = "0.13" cortex-m = "0.7" # Keep in sync with stm32l0xx-hal +cortex-m-rt = "0.6.15" cortex-m-rtic = "1.0.0" embedded-hal = { version = "0.2.3", features = ["unproven"] } # Keep in sync with stm32l0xx-hal embedded-time = "0.12" # Keep in sync with stm32l0xx-hal diff --git a/firmware/examples/supply_monitor.rs b/firmware/examples/supply_monitor.rs new file mode 100644 index 0000000..ddd25a0 --- /dev/null +++ b/firmware/examples/supply_monitor.rs @@ -0,0 +1,108 @@ +//! Prints the supply voltage monitor output to the serial port. +#![no_main] +#![no_std] + +use panic_persist as _; + +use core::fmt::Write; + +use cortex_m_rt::entry; +use embedded_time::rate::Baud; +use hal::serial; +use stm32l0xx_hal as hal; +use stm32l0xx_hal::prelude::*; + +use gfroerli_firmware::supply_monitor; + +#[entry] +fn main() -> ! { + let p = cortex_m::Peripherals::take().unwrap(); + let dp = stm32l0xx_hal::pac::Peripherals::take().unwrap(); + + let syst = p.SYST; + let mut rcc = dp.RCC.freeze(hal::rcc::Config::hsi16()); + let mut delay = hal::delay::Delay::new(syst, rcc.clocks); + + let gpiob = dp.GPIOB.split(&mut rcc); + let mut led = gpiob.pb3.into_push_pull_output(); + + let gpioa = dp.GPIOA.split(&mut rcc); + + /* + // Nucleo serial + let mut serial = hal::serial::Serial::usart2( + dp.USART2, + gpioa.pa2.into_floating_input(), + gpioa.pa3.into_floating_input(), + hal::serial::Config { + baudrate: Baud(9600), + wordlength: hal::serial::WordLength::DataBits8, + parity: hal::serial::Parity::ParityNone, + stopbits: hal::serial::StopBits::STOP1, + }, + &mut rcc, + ) + .unwrap(); + */ + + let mut serial = hal::serial::Serial::usart1( + dp.USART1, + gpiob.pb6.into_floating_input(), + gpiob.pb7.into_floating_input(), + serial::Config { + baudrate: Baud(57_600), + wordlength: serial::WordLength::DataBits8, + parity: serial::Parity::ParityNone, + stopbits: serial::StopBits::STOP1, + }, + &mut rcc, + ) + .unwrap(); + + writeln!(serial, "Starting supply_monitor example").unwrap(); + + // Initialize supply monitor + let adc = dp.ADC.constrain(&mut rcc); + + let a1 = gpioa.pa1.into_analog(); + let adc_enable_pin = gpioa.pa5.into_push_pull_output().downgrade(); + let mut supply_monitor = supply_monitor::SupplyMonitor::new(a1, adc, adc_enable_pin); + + loop { + supply_monitor.enable(); + if let Some(vref_raw) = supply_monitor.read_vref_raw() { + let v_input = (vref_raw as f32) / 4095.0 * 3.3; + let v_dda = supply_monitor::SupplyMonitor::convert_vrefint_to_vdda(vref_raw); + + writeln!( + serial, + "VREF: {} ({}) -> VDDA: {}", + vref_raw, v_input, v_dda + ) + .unwrap(); + + let v_supply = supply_monitor.read_supply_raw(); + if let Some(v_supply_raw) = v_supply { + // real ca. 0.7V@3.3V + let v_input = (v_supply_raw as f32) / 4095.0 * 3.3; + let v_supply_converted_old = + supply_monitor::SupplyMonitor::convert_input(v_supply_raw, 3.3); + let v_supply_converted = + supply_monitor::SupplyMonitor::convert_input(v_supply_raw, v_dda); + + writeln!( + serial, + "Raw: {} ({}) -> Supply: {} ({} old)", + v_supply_raw, v_input, v_supply_converted, v_supply_converted_old + ) + .unwrap(); + } + } + //supply_monitor.disable(); + + led.set_high().unwrap(); + delay.delay_ms(1_000u16); + led.set_low().unwrap(); + delay.delay_ms(1_000u16); + } +} diff --git a/firmware/src/main.rs b/firmware/src/main.rs index ef7ea9b..9c3e3c2 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -563,7 +563,7 @@ mod app { if let Some(v_supply_f32) = v_supply .as_ref() .map(U12::as_u16) - .map(SupplyMonitor::convert_input) + .map(|v| SupplyMonitor::convert_input(v, 3.3)) { delimit!(); write!(ctx.shared.debug, "VDD: {:.3}V", v_supply_f32).unwrap(); diff --git a/firmware/src/supply_monitor.rs b/firmware/src/supply_monitor.rs index 6f90f6a..4bb1adc 100644 --- a/firmware/src/supply_monitor.rs +++ b/firmware/src/supply_monitor.rs @@ -1,6 +1,7 @@ use embedded_hal::{adc::OneShot, digital::v2::OutputPin}; use stm32l0xx_hal::{ - adc::{self, Adc, Align}, + adc::{self, Adc, Align, VRef}, + calibration::VrefintCal, gpio::{gpioa::PA1, Analog, Output, Pin, PushPull}, }; @@ -14,6 +15,10 @@ pub struct SupplyMonitor { } impl SupplyMonitor { + const ADC_MAX: f32 = 4095.0; + /// Voltage at which VREFINT_CAL got calibrated + const VREFINT_CAL_VDD: f32 = 3.0; + pub fn new( adc_pin: PA1, mut adc: Adc, @@ -21,7 +26,8 @@ impl SupplyMonitor { ) -> Self { adc.set_precision(adc::Precision::B_12); adc.set_align(Align::Right); // Use 12 least-significant bits to encode data - adc.set_sample_time(adc::SampleTime::T_79_5); + //adc.set_sample_time(adc::SampleTime::T_79_5); + adc.set_sample_time(adc::SampleTime::T_160_5); SupplyMonitor { adc_pin, adc, @@ -29,27 +35,46 @@ impl SupplyMonitor { } } - /// Disable the supply voltage monitoring voltage divider - fn disable(&mut self) { + /// Disable the supply voltage monitoring voltage divider and VREFINT + pub fn disable(&mut self) { self.enable_pin.set_low().unwrap(); + VRef.disable(&mut self.adc); } - /// Enable the supply voltage monitoring voltage divider - fn enable(&mut self) { + /// Enable the supply voltage monitoring voltage divider and VREFINT + pub fn enable(&mut self) { self.enable_pin.set_high().unwrap(); + VRef.enable(&mut self.adc); } /// Read the supply voltage ADC channel. /// - /// `enable` and `disable` the supply voltage monitoring voltage divider - /// before and after the measurement. + /// Make sure to call `enable` and wait 3ms before the measurement. pub fn read_supply_raw(&mut self) -> Option { - self.enable(); - let val: Option = self.adc.read(&mut self.adc_pin).ok(); - self.disable(); + self.adc.read(&mut self.adc_pin).ok() + } + + /// Read the VREFINT value + pub fn read_vref_raw(&mut self) -> Option { + let val = self.adc.read(&mut VRef).ok(); val } + /// Calcultate the VREFINT voltage from the calibrated value which was calibrated at 3.0V VDDA + pub fn calculate_vref_int_voltage() -> f32 { + Self::VREFINT_CAL_VDD / Self::ADC_MAX * (VrefintCal::get().read() as f32) + } + + /// Convert the raw VREFINT ADC value to VDDA + pub fn convert_vrefint_to_vdda(vrefint: u16) -> f32 { + Self::VREFINT_CAL_VDD * (VrefintCal::get().read() as f32) / (vrefint as f32) + } + + /// Read VREFINT and calculate VDDA from it + pub fn read_vdda(&mut self) -> Option { + self.read_vref_raw().map(Self::convert_vrefint_to_vdda) + } + /// Read the supply voltage (see `read_supply_raw` for details) and return /// the raw data as `U12`. pub fn read_supply_raw_u12(&mut self) -> Option { @@ -60,16 +85,15 @@ impl SupplyMonitor { /// the voltage in volts as `f32`. pub fn read_supply(&mut self) -> Option { let val = self.read_supply_raw()?; - Some(Self::convert_input(val)) + let vdda = self.read_vdda()?; + Some(Self::convert_input(val, vdda)) } /// Convert the raw ADC value to the resulting supply voltage - pub fn convert_input(input: u16) -> f32 { - const SUPPLY_VOLTAGE: f32 = 3.3; - const ADC_MAX: f32 = 4095.0; + pub fn convert_input(input: u16, vdda: f32) -> f32 { const R_1: f32 = 9.31; const R_2: f32 = 6.04; - (input as f32) / ADC_MAX * SUPPLY_VOLTAGE / R_1 * (R_1 + R_2) + (input as f32) / Self::ADC_MAX * vdda / R_1 * (R_1 + R_2) } }