diff --git a/CHANGELOG.md b/CHANGELOG.md index f129835a..3defa221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,19 +7,26 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] -- Implement `Ptr`, `Sealed`, `Steal` for generic `Periph` -- Unmacro `Adc` -- Use `write` instead of `modify` to clear flags -- Bump `stm32f4-staging` to 0.18, update other dependencies -- `serial` mod refactor + - Implement `Ptr`, `Sealed`, `Steal` for generic `Periph` [#834] + - Unmacro `Adc` [#832] + - Use `write` instead of `modify` to clear flags [#829] + - Bump `stm32f4-staging` to 0.18, update other dependencies [#831] + - `serial` mod refactor [#833] + - FMPI2c APB timings [#770] - Fefactor FMPI2c `embedded-hal` implementations [#784] +[#770]: https://github.com/stm32-rs/stm32f4xx-hal/pull/770 [#784]: https://github.com/stm32-rs/stm32f4xx-hal/pull/784 +[#829]: https://github.com/stm32-rs/stm32f4xx-hal/pull/829 +[#831]: https://github.com/stm32-rs/stm32f4xx-hal/pull/831 +[#832]: https://github.com/stm32-rs/stm32f4xx-hal/pull/832 +[#833]: https://github.com/stm32-rs/stm32f4xx-hal/pull/833 +[#834]: https://github.com/stm32-rs/stm32f4xx-hal/pull/834 ## [v0.22.1] - 2024-11-03 - - Fix pac `defmt` feature - - Fix timer interrupt status clear + - Fix pac `defmt` feature [#807] + - Fix timer interrupt status clear [#821] ## [v0.22.0] - 2024-10-04 @@ -76,6 +83,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). [#796]: https://github.com/stm32-rs/stm32f4xx-hal/pull/796 [#798]: https://github.com/stm32-rs/stm32f4xx-hal/pull/798 [#802]: https://github.com/stm32-rs/stm32f4xx-hal/pull/802 +[#807]: https://github.com/stm32-rs/stm32f4xx-hal/pull/807 +[#821]: https://github.com/stm32-rs/stm32f4xx-hal/pull/821 ## [v0.21.0] - 2024-05-30 diff --git a/Cargo.toml b/Cargo.toml index 454a8406..d8ba37ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -520,7 +520,7 @@ dsihost = ["embedded-display-controller"] eth = [] fmc = [] fsmc = [] -fmpi2c1 = [] +fmpi2c1 = ["dep:micromath"] gpiod = [] gpioe = [] gpiof = [] diff --git a/examples/display-touch.rs b/examples/display-touch.rs index 1976403e..11857897 100644 --- a/examples/display-touch.rs +++ b/examples/display-touch.rs @@ -1,4 +1,4 @@ -//! +//! //! Demonstrates use of I2C bus to configure and use FT6x06 touchscreen controller //! //! Hardware Required: STM32F412G-DISCO board or STM32F413H-DISCO board diff --git a/src/fmpi2c.rs b/src/fmpi2c.rs index ade18d5c..d455498c 100644 --- a/src/fmpi2c.rs +++ b/src/fmpi2c.rs @@ -3,9 +3,10 @@ use core::ops::Deref; use crate::gpio; use crate::pac::fmpi2c1 as i2c1; -use crate::pac::{self, RCC}; -use crate::rcc::{BusClock, Enable, Reset}; +use crate::pac::{self, rcc, RCC}; +use crate::rcc::{BusClock, Clocks, Enable, Reset}; use fugit::{HertzU32 as Hertz, RateExtU32}; +use micromath::F32Ext; #[path = "i2c/common.rs"] mod common; @@ -21,6 +22,8 @@ mod hal_02; #[path = "i2c/hal_1.rs"] mod hal_1; +type I2cSel = rcc::dckcfgr2::FMPI2C1SEL; + pub trait Instance: crate::Sealed + crate::Ptr @@ -30,7 +33,7 @@ pub trait Instance: + BusClock + gpio::alt::I2cCommon { - fn clock_hsi(rcc: &crate::pac::rcc::RegisterBlock); + fn set_clock_source(rcc: &rcc::RegisterBlock, source: I2cSel); } macro_rules! i2c { @@ -38,8 +41,8 @@ macro_rules! i2c { pub type $I2Calias = I2c<$I2C>; impl Instance for $I2C { - fn clock_hsi(rcc: &crate::pac::rcc::RegisterBlock) { - rcc.dckcfgr2().modify(|_, w| w.$i2csel().hsi()); + fn set_clock_source(rcc: &rcc::RegisterBlock, source: I2cSel) { + rcc.dckcfgr2().modify(|_, w| w.$i2csel().variant(source)); } } }; @@ -59,6 +62,7 @@ pub enum Mode { Standard { frequency: Hertz }, Fast { frequency: Hertz }, FastPlus { frequency: Hertz }, + Custom { timing_r: I2cTiming }, } impl Mode { @@ -74,13 +78,13 @@ impl Mode { Self::FastPlus { frequency } } - pub fn get_frequency(&self) -> Hertz { + /*pub fn get_frequency(&self) -> Hertz { match *self { Self::Standard { frequency } => frequency, Self::Fast { frequency } => frequency, Self::FastPlus { frequency } => frequency, } - } + }*/ } impl From for Mode { @@ -97,29 +101,168 @@ impl From for Mode { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum ClockSource<'a> { + Apb(&'a Clocks), + Hsi, +} + +impl<'a> From<&'a Clocks> for ClockSource<'a> { + fn from(value: &'a Clocks) -> Self { + Self::Apb(value) + } +} + +// hddat and vddat are removed because SDADEL is always going to be 0 in this implementation so +// condition is always met +struct I2cSpec { + freq_max: Hertz, + sudat_min: u32, + _lscl_min: u32, + _hscl_min: u32, + trise_max: u32, // in ns + _tfall_max: u32, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct I2cTiming { + pub presc: u8, + pub scldel: u8, + pub sdadel: u8, + pub sclh: u8, + pub scll: u8, +} + +// everything is in nano seconds +const I2C_STANDARD_MODE_SPEC: I2cSpec = I2cSpec { + freq_max: Hertz::Hz(102400), + sudat_min: 250, + _lscl_min: 4700, + _hscl_min: 4000, + trise_max: 640, + _tfall_max: 20, +}; +const I2C_FAST_MODE_SPEC: I2cSpec = I2cSpec { + freq_max: Hertz::Hz(409600), + sudat_min: 100, + _lscl_min: 1300, + _hscl_min: 600, + trise_max: 250, + _tfall_max: 100, +}; + +const I2C_FAST_PLUS_MODE_SPEC: I2cSpec = I2cSpec { + freq_max: Hertz::kHz(1024), + sudat_min: 50, + _lscl_min: 500, + _hscl_min: 260, + trise_max: 60, + _tfall_max: 100, +}; + +fn calculate_timing( + spec: I2cSpec, + i2c_freq: Hertz, + scl_freq: Hertz, + an_filter: bool, + dnf: u8, +) -> I2cTiming { + let i2c_freq = i2c_freq.raw(); + // frequency limit check + assert!(scl_freq <= spec.freq_max); + let scl_freq = scl_freq.raw(); + // T_sync or delay introduced in SCL + // generally it is 2-3 clock cycles + // t_sync + dnf delay + let t_dnf = (dnf) as f32 / i2c_freq as f32; + // if analog filter is enabled then it offer about 50 - 70 ns delay + let t_af: f32 = if an_filter { + 40.0 / 1_000_000_000f32 + } else { + 0.0 + }; + // t_sync = 2 to 3 * i2cclk + let t_sync = 2.0 / (i2c_freq as f32); + // fall or rise time + let t_fall: f32 = 50f32 / 1_000_000_000f32; + let t_rise: f32 = 60f32 / 1_000_000_000f32; + let t_delay = t_fall + t_rise + 2.0 * (t_dnf + t_af + t_sync); + // formula is like F_i2cclk/F/F_scl_clk = (scl_h+scl_l+2)*(Presc + 1) + // consider scl_l+scl_h is 256 max. but in that case clock should always + // be 50% duty cycle. lets consider scl_l+scl_h to be 128. so that it can + // be changed later + // (scl_l+scl_h+2)(presc +1 ) ==> as scl_width*presc ==F_i2cclk/F/F_scl_clk + let product: f32 = (1.0 - t_delay * (scl_freq as f32)) * (i2c_freq / scl_freq) as f32; + let scl_l: u8; + let scl_h: u8; + let mut presc: u8; + // if ratio is > (scll+sclh)*presc. that frequancy is not possible to generate. so + // minimum frequancy possible is generated + if product > 8192 as f32 { + // TODO: should we panic or use minimum allowed frequancy + scl_l = 0x7fu8; + scl_h = 0x7fu8; + presc = 0xfu8; + } else { + // smaller the minimum devition less difference between expected vs + // actual scl clock + let mut min_deviation = 16f32; + // TODO: use duty cycle and based on that use precstart + let presc_start = (product / 512.0).ceil() as u8; + presc = presc_start; + for tmp_presc in presc_start..17 { + let deviation = product % tmp_presc as f32; + if min_deviation > deviation { + min_deviation = deviation; + presc = tmp_presc as u8; + } + } + // now that we have optimal prescalar value. optimal scl_l and scl_h + // needs to be calculated + let scl_width = (product / presc as f32) as u16; // it will be always less than 256 + scl_h = (scl_width / 2 - 1) as u8; + scl_l = (scl_width - scl_h as u16 - 1) as u8; // This is to get max precision + presc -= 1; + } + let scldel: u8 = (((spec.trise_max + spec.sudat_min) as f32 / 1_000_000_000.0) + / ((presc + 1) as f32 / i2c_freq as f32) + - 1.0) + .ceil() as u8; + I2cTiming { + presc, + scldel, + sdadel: 0, + sclh: scl_h, + scll: scl_l, + } +} + pub trait I2cExt: Sized + Instance { - fn i2c( + fn i2c<'a>( self, pins: (impl Into, impl Into), mode: impl Into, + clocks: impl Into>, ) -> I2c; } impl I2cExt for I2C { - fn i2c( + fn i2c<'a>( self, pins: (impl Into, impl Into), mode: impl Into, + clocks: impl Into>, ) -> I2c { - I2c::new(self, pins, mode) + I2c::new(self, pins, mode, clocks) } } impl I2c { - pub fn new( + pub fn new<'a>( i2c: I2C, pins: (impl Into, impl Into), mode: impl Into, + clocks: impl Into>, ) -> Self { unsafe { // Enable and reset clock. @@ -130,7 +273,7 @@ impl I2c { let pins = (pins.0.into(), pins.1.into()); let i2c = I2c { i2c, pins }; - i2c.i2c_init(mode); + i2c.i2c_init(mode, clocks.into()); i2c } @@ -140,59 +283,100 @@ impl I2c { } impl I2c { - fn i2c_init(&self, mode: impl Into) { + fn i2c_init(&self, mode: impl Into, clocks: ClockSource<'_>) { let mode = mode.into(); // Make sure the I2C unit is disabled so we can configure it self.i2c.cr1().modify(|_, w| w.pe().clear_bit()); - // NOTE(unsafe) this reference will only be used for atomic writes with no side effects. - let rcc = unsafe { &(*RCC::ptr()) }; - I2C::clock_hsi(rcc); - - // Calculate settings for I2C speed modes - let presc; - let scldel; - let sdadel; - let sclh; - let scll; - - // We're using the HSI clock to keep things simple so this is going to be always 16 MHz - const FREQ: u32 = 16_000_000; - - // Normal I2C speeds use a different scaling than fast mode below and fast mode+ even more - // below - match mode { - Mode::Standard { frequency } => { - presc = 3; - scll = crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 1, 255) as u8; - sclh = scll - 4; - sdadel = 2; - scldel = 4; - } - Mode::Fast { frequency } => { - presc = 1; - scll = crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 1, 255) as u8; - sclh = scll - 6; - sdadel = 2; - scldel = 3; + let cr1 = self.i2c.cr1().read(); + let an_filter: bool = cr1.anfoff().is_enabled(); + let dnf = cr1.dnf().bits(); + + let i2c_timingr = match clocks { + ClockSource::Apb(clocks) => { + // NOTE(unsafe) this reference will only be used for atomic writes with no side effects. + unsafe { + let rcc = &(*RCC::ptr()); + I2C::set_clock_source(rcc, I2cSel::Apb); + } + let pclk = I2C::clock(clocks); + match mode { + Mode::Standard { frequency } => { + calculate_timing(I2C_STANDARD_MODE_SPEC, pclk, frequency, an_filter, dnf) + } + Mode::Fast { frequency } => { + calculate_timing(I2C_FAST_MODE_SPEC, pclk, frequency, an_filter, dnf) + } + Mode::FastPlus { frequency } => { + calculate_timing(I2C_FAST_PLUS_MODE_SPEC, pclk, frequency, an_filter, dnf) + } + Mode::Custom { timing_r } => timing_r, + } } - Mode::FastPlus { frequency } => { - presc = 0; - scll = crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 4, 255) as u8; - sclh = scll - 2; - sdadel = 0; - scldel = 2; + ClockSource::Hsi => { + // NOTE(unsafe) this reference will only be used for atomic writes with no side effects. + unsafe { + let rcc = &(*RCC::ptr()); + I2C::set_clock_source(rcc, I2cSel::Hsi); + } + + // We're using the HSI clock to keep things simple so this is going to be always 16 MHz + const FREQ: u32 = 16_000_000; + // Normal I2C speeds use a different scaling than fast mode below and fast mode+ even more + // below + match mode { + Mode::Standard { frequency } => { + let presc = 3; + let scll = + crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 1, 255) + as u8; + I2cTiming { + presc, + scldel: 4, + sdadel: 2, + sclh: scll - 4, + scll, + } + } + Mode::Fast { frequency } => { + let presc = 1; + let scll = + crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 1, 255) + as u8; + I2cTiming { + presc, + scldel: 3, + sdadel: 2, + sclh: scll - 6, + scll, + } + } + Mode::FastPlus { frequency } => { + let presc = 0; + let scll = + crate::max_u32((((FREQ >> presc) >> 1) / frequency.raw()) - 4, 255) + as u8; + I2cTiming { + presc, + scldel: 2, + sdadel: 0, + sclh: scll - 2, + scll, + } + } + Mode::Custom { timing_r } => timing_r, + } } - } + }; // Enable I2C signal generator, and configure I2C for configured speed self.i2c.timingr().write(|w| { - w.presc().set(presc); - w.scldel().set(scldel); - w.sdadel().set(sdadel); - w.sclh().set(sclh); - w.scll().set(scll) + w.presc().set(i2c_timingr.presc); + w.scldel().set(i2c_timingr.scldel); + w.sdadel().set(i2c_timingr.sdadel); + w.sclh().set(i2c_timingr.sclh); + w.scll().set(i2c_timingr.scll) }); // Enable the I2C processing