rtic-rs / rtic

Real-Time Interrupt-driven Concurrency (RTIC) framework for ARM Cortex-M microcontrollers
https://rtic.rs
Apache License 2.0
1.8k stars 206 forks source link

embedded-hal-bus with rtic v2 in embedded-hal 1.0.0 #886

Closed pdgilbert closed 7 months ago

pdgilbert commented 9 months ago

I have several examples that share devices on an i2c bus. Both rtic (v2) versions and non-rtic versions worked previously using crate shared-bus. Converting to embedded-hal_1.0.0 I am also converting to embedded-hal-bus to share the i2c bus.

With help and considerable patience from @bugadani I now have a working non-rtic version of an example using crates ssd1305, ads1x1x, and ina219. Attempting the rtic version I have run into two stumbling blocks. The first is that I cannot get the RefCellDevice argument to live long enough. I think I need to specify that the lifetime is static but I have not managed to do that correctly. I get the same error with both stm32fxx_hal and stm32h7xx_hal. The code and error messages are below.

Click to expand code ``` #![deny(unsafe_code)] #![no_std] #![no_main] #![feature(type_alias_impl_trait)] #[cfg(debug_assertions)] use panic_semihosting as _; #[cfg(not(debug_assertions))] use panic_halt as _; use rtic::app; #[cfg_attr(feature = "stm32f4xx", app(device = stm32f4xx_hal::pac, dispatchers = [TIM2, TIM3]))] #[cfg_attr(feature = "stm32h7xx", app(device = stm32h7xx_hal::pac, dispatchers = [TIM2, TIM3]))] mod app { use rtic; use rtic_monotonics::systick::Systick; use rtic_monotonics::systick::fugit::{ExtU32}; const MONOCLOCK: u32 = 8_000_000; const READ_INTERVAL: u32 = 10; // used as seconds const BLINK_DURATION: u32 = 20; // used as milliseconds ///////////////////// ads use ads1x1x::{Ads1x1x, channel, ChannelSelection, DynamicOneShot, mode::OneShot, //Ads1015, Resolution12Bit, PRIVATE? FullScaleRange, SlaveAddr}; ///////////////////// ina use ina219::{address::{Address, Pin}, measurements::BusVoltage, SyncIna219, calibration::UnCalibrated, }; ///////////////////// ssd use ssd1306::{mode::BufferedGraphicsMode, prelude::*, I2CDisplayInterface, Ssd1306}; type DisplaySizeType = ssd1306::prelude::DisplaySize128x32; const DISPLAYSIZE: DisplaySizeType = DisplaySize128x32; const VPIX:i32 = 12; // vertical pixels for a line, including space use core::fmt::Write; use embedded_graphics::{ mono_font::{ascii::FONT_6X10 as FONT, MonoTextStyle, MonoTextStyleBuilder}, pixelcolor::BinaryColor, prelude::*, text::{Baseline, Text}, }; ///////////////////// hals use core::cell::RefCell; use embedded_hal_bus::i2c::RefCellDevice; use embedded_hal::{ //i2c::I2c as I2cTrait, delay::DelayNs, }; #[cfg(feature = "stm32f4xx")] use stm32f4xx_hal as hal; #[cfg(feature = "stm32h7xx")] use stm32h7xx_hal as hal; use hal::{ pac::{Peripherals, I2C1}, i2c::I2c as I2cType, rcc::{RccExt}, prelude::*, }; #[cfg(feature = "stm32h7xx")] use stm32h7xx_hal::{ delay::DelayFromCountDownTimer, }; #[cfg(feature = "stm32f4xx")] pub fn setup_from_dp(dp: Peripherals) -> ( I2cType, impl DelayNs) { let rcc = dp.RCC.constrain(); let clocks = rcc.cfgr.freeze(); let gpiob = dp.GPIOB.split(); let scl = gpiob.pb8.into_alternate_open_drain(); let sda = gpiob.pb9.into_alternate_open_drain(); let i2c = dp.I2C1.i2c( (scl, sda), 400.kHz(), &clocks); // need ::<1000000_u32> for `FREQ` of the method `delay WHY? let delay = dp.TIM5.delay::<1000000_u32>(&clocks); (i2c, delay) } #[cfg(feature = "stm32h7xx")] pub fn setup_from_dp(dp: Peripherals) -> ( I2cType, impl DelayNs) { let rcc = dp.RCC.constrain(); let vos = dp.PWR.constrain().freeze(); let ccdr = rcc.sys_ck(100.MHz()).freeze(vos, &dp.SYSCFG); let clocks = ccdr.clocks; let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); let scl = gpiob.pb8.into_alternate().set_open_drain(); let sda = gpiob.pb9.into_alternate().set_open_drain(); let i2c = dp.I2C1.i2c((scl, sda), 400.kHz(), ccdr.peripheral.I2C1, &clocks); // CountDownTimer not supported by embedded-hal 1.0.0 let timer = dp.TIM5.timer(1.Hz(), ccdr.peripheral.TIM5, &clocks); let delay = DelayFromCountDownTimer::new(timer); (i2c, delay) } /////////////////////////////////////////////////////////////////////////////////////////// fn show_display( voltage: BusVoltage, a_mv: i16, b_mv: [i16; 2], text_style: MonoTextStyle, display: &mut Ssd1306>, ) -> () where S: DisplaySize, { let mut lines: [heapless::String<32>; 3] = [ heapless::String::new(), heapless::String::new(), heapless::String::new(), ]; write!(lines[0], "ina V: {:?} mv? ", voltage).unwrap(); write!(lines[1], "ads_a V: {} mv ", a_mv).unwrap(); write!(lines[2], "ads_b A0: {} mv. ads_b A1: {} mv.", b_mv[0], b_mv[1]).unwrap(); display.clear_buffer(); for (i, line) in lines.iter().enumerate() { // start from 0 requires that the top is used for font baseline Text::with_baseline( line, Point::new(0, i as i32 * VPIX), text_style, Baseline::Top, ) .draw(&mut *display) .unwrap(); } display.flush().unwrap(); () } //////////////////////////////////////////////////////////////////////////////// #[init] fn init(cx: init::Context) -> (Shared, Local ) { let mono_token = rtic_monotonics::create_systick_token!(); Systick::start(cx.core.SYST, MONOCLOCK, mono_token); let (i2cset, _delay) = setup_from_dp(cx.device); let i2cset_ref_cell = RefCell::new(i2cset); let adc_a_rcd = RefCellDevice::new(&i2cset_ref_cell); let adc_b_rcd = RefCellDevice::new(&i2cset_ref_cell); let ina_rcd = RefCellDevice::new(&i2cset_ref_cell); let ssd_rcd = RefCellDevice::new(&i2cset_ref_cell); ///////////////////// ads let mut adc_a = Ads1x1x::new_ads1015(adc_a_rcd, SlaveAddr::Alternative(false, false)); //addr = GND let mut adc_b = Ads1x1x::new_ads1015(adc_b_rcd, SlaveAddr::Alternative(false, true)); //addr = V // set FullScaleRange to measure expected max voltage. adc_a.set_full_scale_range(FullScaleRange::Within4_096V).unwrap(); adc_b.set_full_scale_range(FullScaleRange::Within4_096V).unwrap(); ///////////////////// ina let ina = SyncIna219::new( ina_rcd, Address::from_pins(Pin::Gnd, Pin::Gnd)).unwrap(); ///////////////////// ssd let interface = I2CDisplayInterface::new(ssd_rcd); //default address 0x3C //let interface = I2CDisplayInterface::new_custom_address(ssd_rcd, 0x3D); //alt address let mut display = Ssd1306::new(interface, DISPLAYSIZE, DisplayRotation::Rotate0) .into_buffered_graphics_mode(); display.init().unwrap(); let text_style = MonoTextStyleBuilder::new() .font(&FONT) .text_color(BinaryColor::On) .build(); Text::with_baseline( "Display initialized ...", Point::zero(), text_style, Baseline::Top, ) .draw(&mut display) .unwrap(); //////////////////////////// let led:u8 = 8; //FAKE (Shared {led}, Local { ina, display, text_style }) //, adc_a, adc_b } #[shared] struct Shared { led: u8, //LedType, } #[local] struct Local { display: Ssd1306>>, DisplaySizeType, BufferedGraphicsMode>, //text_style: TextStyle, text_style: MonoTextStyle<'static, BinaryColor>, ina: SyncIna219>, UnCalibrated>, // adc_a: Ads1x1x>, Ads1015, Resolution12Bit, ads1x1x::mode::OneShot>, // adc_b: Ads1x1x>, Ads1015, Resolution12Bit, ads1x1x::mode::OneShot>, } ///////////////////// measure and display #[task(local = [ ina, display, text_style])] // adc_a, adc_b async fn read_and_display(cx: read_and_display::Context) { let ina = cx.local.ina; // let adc_a = cx.local.adc_a; // let adc_b = cx.local.adc_b; let mut display = cx.local.display; let text_style = cx.local.text_style; loop { let voltage = ina.bus_voltage().unwrap(); // let a_mv = block!(DynamicOneShot::read(&mut adc_a, ChannelSelection::SingleA0)).unwrap_or(8091); // let values_b = [ // block!(adc_b.read(channel::SingleA0)).unwrap_or(8091), // block!(adc_b.read(channel::SingleA1)).unwrap_or(8091), // ]; let a_mv: i16 = 0; //FAKE let values_b: [i16; 2] = [0, 0]; //FAKE show_display(voltage, a_mv, values_b, *text_style, &mut display); Systick::delay(READ_INTERVAL.secs()).await; } } } ```
Click to expand Cargo.toml ``` [package] authors = ["pdGilbert"] categories = ["embedded", "no-std"] description = "ads, ina, ssd, embedded-hal-bus example" keywords = ["driver", "i2c", "embedded-hal-bus", "example"] license = "MIT OR Apache-2.0" name = "embedded-hal-bus_example" version = "0.0.1" edition = "2021" [dependencies] stm32f4xx-hal = { version = "0.20.0", optional = true } stm32h7xx-hal = { git = "https://github.com/stm32-rs/stm32h7xx-hal", optional = true , branch = "eh-v1.0"} ssd1306 = { git = "https://github.com/bugadani/ssd1306", branch = "ehal1" } ads1x1x = { git = "https://github.com/eldruin/ads1x1x-rs" } ina219 = { git = "https://github.com/tdittr/ina219", branch = "add-configuration-register" } # has eh1.0.0 rtic = { git = "https://github.com/rtic-rs/rtic", features=["thumbv7-backend", "rtic-monotonics"], optional = true } rtic-monotonics = { git = "https://github.com/rtic-rs/rtic", features = [ "cortex-m-systick"], optional = true } embedded-hal = "1.0" embedded-hal-bus = "0.1" #embedded-hal = { git = "https://github.com/rust-embedded/embedded-hal/" } #embedded-hal-bus = { git = "https://github.com/rust-embedded/embedded-hal/" } embedded-graphics = ">=0.7" heapless = "0.7" cortex-m-rt = ">=0.7.0" panic-semihosting = { version = ">=0.5.2" } [features] stm32f4xx = ["stm32f4xx-hal", "rtic", "rtic-monotonics" ] stm32h7xx = ["stm32h7xx-hal/rt", "rtic", "rtic-monotonics" ] stm32f401 = ["stm32f4xx-hal/stm32f401" ] stm32f411 = ["stm32f4xx-hal/stm32f411" ] stm32h742 = ["stm32h7xx-hal/stm32h742" ] ```
Click to expand compile errors with stm32f4xx_hal ``` $ cargo build --no-default-features --target thumbv7em-none-eabihf --features stm32f401,stm32f4xx --example ads_ina_ssd_rtic Compiling proc-macro2 v1.0.78 Compiling unicode-ident v1.0.12 Compiling cortex-m v0.7.7 Compiling nb v1.1.0 Compiling semver-parser v0.7.0 Compiling critical-section v1.1.2 Compiling embedded-hal v1.0.0 Compiling syn v1.0.109 Compiling nb v0.1.3 Compiling vcell v0.1.3 Compiling version_check v0.9.4 Compiling void v1.0.2 Compiling volatile-register v0.2.2 Compiling embedded-hal v0.2.7 Compiling semver v0.9.0 Compiling bitfield v0.13.2 Compiling embedded-hal-async v1.0.0 Compiling rustc_version v0.2.3 Compiling semver v1.0.21 Compiling cortex-m-rt v0.7.3 Compiling portable-atomic v1.6.0 Compiling byteorder v1.5.0 Compiling bare-metal v0.2.5 Compiling proc-macro-error-attr v1.0.4 Compiling gcd v2.3.0 Compiling autocfg v1.1.0 Compiling az v1.2.1 Compiling fugit v0.3.7 Compiling quote v1.0.35 Compiling syn v2.0.48 Compiling proc-macro-error v1.0.4 Compiling futures-core v0.3.30 Compiling num-traits v0.2.17 Compiling pin-utils v0.1.0 Compiling pin-project-lite v0.2.13 Compiling futures-task v0.3.30 Compiling atomic-polyfill v1.0.3 Compiling futures-util v0.3.30 Compiling rtic-common v1.0.1 (https://github.com/rtic-rs/rtic#7757d17c) Compiling rustc_version v0.4.0 Compiling equivalent v1.0.1 Compiling stable_deref_trait v1.2.0 Compiling stm32f4 v0.15.1 Compiling powerfmt v0.2.0 Compiling bare-metal v1.0.0 Compiling cortex-m-semihosting v0.5.0 Compiling hashbrown v0.14.3 Compiling display-interface v0.5.0 Compiling rtic-monotonics v1.5.0 (https://github.com/rtic-rs/rtic#7757d17c) Compiling heapless v0.7.17 Compiling deranged v0.3.11 Compiling embedded-graphics-core v0.4.0 Compiling indexmap v2.1.0 Compiling rtic-time v1.3.0 (https://github.com/rtic-rs/rtic#7757d17c) Compiling ssd1306 v0.8.4 (https://github.com/bugadani/ssd1306?branch=ehal1#0bae3a66) Compiling litrs v0.4.1 Compiling stm32f4xx-hal v0.20.0 Compiling embedded-hal-bus v0.1.0 Compiling ina219 v0.2.0 (https://github.com/tdittr/ina219?branch=add-configuration-register#0b13baf2) Compiling byte-slice-cast v1.2.2 Compiling time-core v0.1.2 Compiling rtic v2.0.1 (https://github.com/rtic-rs/rtic#7757d17c) Compiling cfg-if v1.0.0 Compiling display-interface-spi v0.5.0 Compiling time v0.3.31 Compiling float-cmp v0.9.0 Compiling display-interface-i2c v0.5.0 Compiling embedded-dma v0.2.0 Compiling fugit-timer v0.1.3 Compiling hash32 v0.2.1 Compiling embedded-hal-nb v1.0.0 Compiling micromath v2.1.0 Compiling rtic-core v1.0.0 Compiling rand_core v0.6.4 Compiling document-features v0.2.8 Compiling embedded-storage v0.3.1 Compiling embedded-graphics v0.8.1 Compiling panic-semihosting v0.6.0 Compiling ads1x1x v0.2.2 (https://github.com/eldruin/ads1x1x-rs#9cc79564) Compiling enumflags2_derive v0.7.8 Compiling cortex-m-rt-macros v0.7.0 Compiling rtic-macros v2.0.1 (https://github.com/rtic-rs/rtic#7757d17c) Compiling enumflags2 v0.7.8 Compiling embedded-hal-bus_example v0.0.1 (rust-integration-testing/ads_ina_ssd) warning: unused imports: `ChannelSelection`, `DynamicOneShot`, `channel`, `mode::OneShot` --> examples/ads_ina_ssd_rtic.rs:29:27 | 29 | use ads1x1x::{Ads1x1x, channel, ChannelSelection, | ^^^^^^^ ^^^^^^^^^^^^^^^^ 30 | DynamicOneShot, mode::OneShot, | ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ | = note: `#[warn(unused_imports)]` on by default error[E0597]: `i2cset_ref_cell` does not live long enough --> examples/ads_ina_ssd_rtic.rs:173:43 | 14 | ...tr(feature = "stm32f4xx", app(device = stm32f4xx_hal::pac, dispatchers = [TIM2, TIM3]))] | - `i2cset_ref_cell` dropped here while still borrowed ... 170 | ...et i2cset_ref_cell = RefCell::new(i2cset); | --------------- binding `i2cset_ref_cell` declared here ... 173 | ...et ina_rcd = RefCellDevice::new(&i2cset_ref_cell); | -------------------^^^^^^^^^^^^^^^^- | | | | | borrowed value does not live long enough | argument requires that `i2cset_ref_cell` is borrowed for `'static` error[E0597]: `i2cset_ref_cell` does not live long enough --> examples/ads_ina_ssd_rtic.rs:174:43 | 14 | ...tr(feature = "stm32f4xx", app(device = stm32f4xx_hal::pac, dispatchers = [TIM2, TIM3]))] | - `i2cset_ref_cell` dropped here while still borrowed ... 170 | ...et i2cset_ref_cell = RefCell::new(i2cset); | --------------- binding `i2cset_ref_cell` declared here ... 174 | ...et ssd_rcd = RefCellDevice::new(&i2cset_ref_cell); | -------------------^^^^^^^^^^^^^^^^- | | | | | borrowed value does not live long enough | argument requires that `i2cset_ref_cell` is borrowed for `'static` For more information about this error, try `rustc --explain E0597`. warning: `embedded-hal-bus_example` (example "ads_ina_ssd_rtic") generated 1 warning error: could not compile `embedded-hal-bus_example` (example "ads_ina_ssd_rtic") due to 2 previous errors; 1 warning emitted ```

The second problem is because there is a need for types rather than impl traits in the fields of Shared and Local structures. (It would be nice if that is wrong, please let me know.) I use setup functions to deal with small differences among hal crates. In the above code I have used types to indicate the results of the setup_from_dp. In the non-rtic code I can use impl I2c to indicate the result of the setup_from_dp. If I do that in the rtic code, and specify types in the Shared and Local structures, the compiler complains expectedI2c, found opaque type. Is there a way to use impl I2c for the result of setup_from_dp and then a type in the structure fields?

Related to this, @eldruin 's ads1x1x works with impl traits in the non-rtic code but is commented out in the code above because some of the actual types are private, so it does not get as far as the lifetime problem in the rtic version.

perlindgren commented 9 months ago
korken89 commented 9 months ago

Hi!

The recommended way to share buses in RTIC (or embassy) is to use the following: https://docs.rs/rtic-sync/latest/rtic_sync/arbiter/i2c/index.html It allows you to share an I2C bus between multiple users. There is also an SPI variant.

Ping us if you have questions!

pdgilbert commented 9 months ago

Thank you @perlindgren and @korken89 . I have a couple of simple quick questions:

        use sensor::Asensor;

I am assuming use sensor::Asensor is a pseudo code reference to "a sensor" crate? Or is it really a crate that I should be looking for?

Could you briefly explain this loop:

        loop {
            // Use scope to make sure I2C access is dropped.
            {
                // Read from sensor driver that wants to use I2C directly.
                let mut i2c = i2c.access().await;
                let status = Asensor::status(&mut i2c).await;
            }

            // Read ENS160 sensor.
            let eco2 = cx.local.ens160.eco2().await;
        }

It seems like Asensor gets created and drop each pass through the loop, while eco2 stays connected. Are they really on a shared bus with their own addresses? What is happening? Do I really need to drop one of the shared connections to use the other one?

andresv commented 9 months ago

It awaits access for shared I2C. If I2C is currently used by some other device in another task (like reading some I2C temp sensor) then it awaits until I2C resource becomes available.

let mut i2c = i2c.access().await;

In this particular example there is Asensor (just an example sensor, it is not a real crate) implementation that needs I2C as an argument to read something. Typically I2C is given to device when created like SomeTempSensor::new(i2c) but this particular example uses a device that needs I2C as a reference.

The thing is when we created let mut i2c = i2c.access().await; it holds access to shared I2C and therefore

let eco2 = cx.local.ens160.eco2().await;

has to await until it gets its access, but it newer does (if we do not use scope to drop it) because let i2c is still available and it has I2C access. That is why it is put into a scope. After scope ends i2c is dropped and let eco2 = cx.local.ens160.eco2().await; can then take access for shared I2C and do its thing.

pdgilbert commented 9 months ago

I'm confused about what "sensor driver that wants to use I2C directly" means in the pseudo-code at https://docs.rs/rtic-sync/latest/rtic_sync/arbiter/i2c/index.html . Is that supposed to be some sensor that for some reason cannot use ArbiterDevice and so has to be given exclusive use of the bus?

Below is my attempt at an example that uses two ens160 sensors on the same bus, one at 0x52 and one at 0x53. I think these can both use ArbiterDevice and it looks after the bus sharing, so I do not need a scope to make sure I2C access is dropped. Is that correct?

After finally figuring out that I have to use features = "unstable" to do

use rtic_sync::{arbiter::{i2c::ArbiterDevice, Arbiter}};

I have managed to get to something fairly close to compiling. It is set up to compile with stm32f4xx-hal and stm32h7xx-hal. I am now back to an earlier question. Where should I2c come from so that it takes a lifetime argument? The code and Cargo.toml are below. The errors are:

Click to expand error messages ``` $ cargo build --no-default-features --target thumbv7em-none-eabihf --features stm32f411,stm32f4xx --example ens160x2_arbit Compiling embedded-hal-bus_example v0.0.1 (/home/paul/githubClones/rust-integration-testing/ens160x2_arbit) error[E0107]: struct takes 0 lifetime arguments but 1 lifetime argument was supplied --> examples/ens160x2_arbit.rs:108:46 | 108 | ens_1: Ens160>>, | ^^^ ------- help: remove this lifetime argument | | | expected 0 lifetime arguments | note: struct defined here, with 0 lifetime parameters --> /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/stm32f4xx-hal-0.20.0/src/i2c.rs:68:12 | 68 | pub struct I2c { | ^^^ error[E0107]: struct takes 0 lifetime arguments but 1 lifetime argument was supplied --> examples/ens160x2_arbit.rs:109:46 | 109 | ens_2: Ens160>>, | ^^^ ------- help: remove this lifetime argument | | | expected 0 lifetime arguments | note: struct defined here, with 0 lifetime parameters --> /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/stm32f4xx-hal-0.20.0/src/i2c.rs:68:12 | 68 | pub struct I2c { | ^^^ error[E0107]: struct takes 0 lifetime arguments but 1 lifetime argument was supplied --> examples/ens160x2_arbit.rs:115:42 | 115 | i2c_arbiter: MaybeUninit>> = MaybeUninit::uninit(), | ^^^ ------- help: remove this lifetime argument | | | expected 0 lifetime arguments | note: struct defined here, with 0 lifetime parameters --> /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/stm32f4xx-hal-0.20.0/src/i2c.rs:68:12 | 68 | pub struct I2c { | ^^^ error[E0107]: struct takes 0 lifetime arguments but 1 lifetime argument was supplied --> examples/ens160x2_arbit.rs:135:74 | 135 | ...&'static Arbiter>) { | ^^^ ------- help: remove this lifetime argument | | | expected 0 lifetime arguments | note: struct defined here, with 0 lifetime parameters --> /home/paul/.cargo/registry/src/index.crates.io-6f17d22bba15001f/stm32f4xx-hal-0.20.0/src/i2c.rs:68:12 | 68 | pub struct I2c { | ^^^ For more information about this error, try `rustc --explain E0107`. error: could not compile `embedded-hal-bus_example` (example "ens160x2_arbit") due to 12 previous errors ```

If I remove the lifetime argument then errors are much worse.

Click to expand code ``` #![deny(unsafe_code)] #![no_std] #![no_main] #![feature(type_alias_impl_trait)] #[cfg(debug_assertions)] use panic_semihosting as _; #[cfg(not(debug_assertions))] use panic_halt as _; use rtic::app; #[cfg_attr(feature = "stm32f4xx", app(device = stm32f4xx_hal::pac, dispatchers = [TIM2, TIM3]))] #[cfg_attr(feature = "stm32h7xx", app(device = stm32h7xx_hal::pac, dispatchers = [TIM2, TIM3]))] mod app { use rtic; use rtic_monotonics::systick::Systick; use rtic_monotonics::systick::fugit::{ExtU32}; use cortex_m_semihosting::{hprintln}; const MONOCLOCK: u32 = 8_000_000; const READ_INTERVAL: u32 = 10; // used as seconds ///////////////////// arbiter use core::mem::MaybeUninit; use rtic_sync::{arbiter::{i2c::ArbiterDevice, Arbiter}}; ///////////////////// ens use ens160::{Ens160, AirQualityIndex, ECo2}; ///////////////////// hals use embedded_hal::{ i2c::I2c as I2cTrait, delay::DelayNs, }; #[cfg(feature = "stm32f4xx")] use stm32f4xx_hal as hal; #[cfg(feature = "stm32h7xx")] use stm32h7xx_hal as hal; use hal::{ pac::{Peripherals, I2C1}, i2c::I2c, //as I2cType, rcc::{RccExt}, prelude::*, }; #[cfg(feature = "stm32h7xx")] use stm32h7xx_hal::{ delay::DelayFromCountDownTimer, }; ///////////////////// setup #[cfg(feature = "stm32f4xx")] pub fn setup_from_dp(dp: Peripherals) -> ( I2c, impl DelayNs) { let rcc = dp.RCC.constrain(); let clocks = rcc.cfgr.freeze(); let gpiob = dp.GPIOB.split(); let scl = gpiob.pb8.into_alternate_open_drain(); let sda = gpiob.pb9.into_alternate_open_drain(); let i2c = dp.I2C1.i2c( (scl, sda), 400.kHz(), &clocks); let delay = dp.TIM5.delay::<1000000_u32>(&clocks); (i2c, delay) } #[cfg(feature = "stm32h7xx")] pub fn setup_from_dp(dp: Peripherals) -> ( I2cType, impl DelayNs) { let rcc = dp.RCC.constrain(); let vos = dp.PWR.constrain().freeze(); let ccdr = rcc.sys_ck(100.MHz()).freeze(vos, &dp.SYSCFG); let clocks = ccdr.clocks; let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); let scl = gpiob.pb8.into_alternate().set_open_drain(); let sda = gpiob.pb9.into_alternate().set_open_drain(); let i2c = dp.I2C1.i2c((scl, sda), 400.kHz(), ccdr.peripheral.I2C1, &clocks); // CountDownTimer not supported by embedded-hal 1.0.0 let timer = dp.TIM5.timer(1.Hz(), ccdr.peripheral.TIM5, &clocks); let delay = DelayFromCountDownTimer::new(timer); (i2c, delay) } //////////////////////////////////////////////////////////////////////////////// #[shared] struct Shared {} #[local] struct Local { ens_1: Ens160>>, ens_2: Ens160>>, } //////////////////////////////////////////////////////////////////////////////// #[init(local = [ i2c_arbiter: MaybeUninit>> = MaybeUninit::uninit(), ])] fn init(cx: init::Context) -> (Shared, Local ) { let mono_token = rtic_monotonics::create_systick_token!(); Systick::start(cx.core.SYST, MONOCLOCK, mono_token); let (i2c, _delay) = setup_from_dp(cx.device); let i2c_arbiter = cx.local.i2c_arbiter.write(Arbiter::new(i2c)); let ens_1 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x52); let ens_2 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x53); i2c_sensors::spawn(i2c_arbiter).ok(); (Shared {}, Local { ens_1, ens_2 }) } #[task(local = [ens_1, ens_2])] async fn i2c_sensors(cx: i2c_sensors::Context, i2c: &'static Arbiter>) { let mut tvoc_1: u16; let mut eco_1: ECo2; let mut aqia_1: AirQualityIndex ; let mut aqib_1: AirQualityIndex ; let mut tvoc_2: u16; let mut eco_2: ECo2; let mut aqia_2: AirQualityIndex ; let mut aqib_2: AirQualityIndex ; let ens_1 = cx.local.ens_1; let ens_2 = cx.local.ens_2; loop { // { // // Use scope to make sure I2C access is dropped. // // Read from sensor driver that wants to use I2C directly. // ?? read data before reading status ?? // let mut i2c = i2c.access().await; // let status = Asensor::status(&mut i2c).await; // } if let Ok(status) = ens_1.status() { if status.data_is_ready() { tvoc_1 = ens_1.tvoc().await; eco_1 = ens_1.eco().await; aqia_1 = AirQualityIndex::try_from(eco_1).unwrap(); // from eco aqib_1 = ens_1.air_quality_index().await; // directly } } if let Ok(status) = ens_2.status() { if status.data_is_ready() { tvoc_1 = ens_2.tvoc().await; eco_1 = ens_2.eco2().await; aqia_1 = AirQualityIndex::try_from(eco_2).unwrap(); // from eco aqia_1 = ens_2.air_quality_index().await; // directly } } hprintln!("S1: TVOC:{} ppb. eco:{:?} AQIa: {:?} b:{:?}", tvoc_1, eco_1, aqia_1, aqib_1).unwrap(); Systick::delay(5.secs()).await; hprintln!("S2: TVOC:{} ppb. eco:{:?} AQIa: {:?} b:{:?}", tvoc_2, eco_2, aqia_2, aqib_2).unwrap(); Systick::delay(5.secs()).await; } } } ```
Click to expand Cargo.toml ``` [package] authors = ["pdGilbert"] categories = ["embedded", "no-std"] description = "embedded-hal-bus example with rtic_sync::arbiter and 2 eco160 sensors" keywords = ["driver", "i2c", "embedded-hal-bus", "example"] license = "MIT OR Apache-2.0" name = "embedded-hal-bus_example" version = "0.0.1" edition = "2021" [dependencies] stm32f4xx-hal = { version = "0.20.0", optional = true } stm32h7xx-hal = { git = "https://github.com/stm32-rs/stm32h7xx-hal", optional = true , branch = "eh-v1.0"} #ens160 = { git = "https://github.com/teamplayer3/ens160", features = ["no-std"], default-features = false } ens160 = { git = "https://github.com/pdgilbert/ens160", features = ["no-std"], default-features = false } rtic = { version = "2.0.1", features=["thumbv7-backend", "rtic-monotonics"], optional = true } rtic-monotonics = { version = "1.5.0", features = [ "cortex-m-systick"], optional = true } rtic-sync = { version = "1.2.0", features = [ "unstable"], optional = true } #rtic = { git = "https://github.com/rtic-rs/rtic", features=["thumbv7-backend", "rtic-monotonics"], optional = true } #rtic-monotonics = { git = "https://github.com/rtic-rs/rtic", features = [ "cortex-m-systick"], optional = true } # this gives use of undeclared crate or module `rtic_sync` ?? #rtic-sync = { git = "https://github.com/rtic-rs/rtic", optional = true } embedded-hal = "1.0" embedded-hal-bus = "0.1" embedded-graphics = ">=0.7" heapless = "0.7" cortex-m-rt = ">=0.7.0" panic-semihosting = { version = ">=0.5.2" } cortex-m-semihosting = { version = "0.3.7" } [features] stm32f4xx = ["stm32f4xx-hal", "rtic", "rtic-monotonics", "rtic-sync" ] stm32h7xx = ["stm32h7xx-hal/rt", "rtic", "rtic-monotonics", "rtic-sync" ] stm32f401 = ["stm32f4xx-hal/stm32f401" ] stm32f411 = ["stm32f4xx-hal/stm32f411" ] stm32h742 = ["stm32h7xx-hal/stm32h742" ] ```
andresv commented 9 months ago

Well "sensor driver that wants to use I2C directly" is probably a quick hack or badly written proof of concept driver. Suppose you just have an single async function to write some bytes to I2C. Notice here i2c is a reference.

    pub async fn reset(i2c: &mut I2c) -> Result<(), i2c::Error> {
        let mut read = [0; 1];
        i2c.write_read(ADDRESS, &[Registers::Ctrl2 as u8], &mut read).await?;
        i2c.write(ADDRESS, &[Registers::Ctrl2 as u8, read[0] | 0x80]).await?;
        Mono::delay(3.millis()).await;
        Ok(())
    }

Do use such function you have to give it a I2C which can be done using Arbiter :

// Request access to shared I2C bus.
let mut i2c = i2c.access().await;
// Here we have access to I2C, at the same time nobody else can use it.
// If some other task is doing `.access().await;`
// then it awaits until we have finished with our `i2c` (meaning our `let i2c` variable is dropped).
asensor::reset(&mut I2c).await. 

// Notice we did not use `drop(i2c)` or scope `{....}` above.
// This means that `i2c` variable still has exclusive access to I2C and
// this waits forever to get also access to I2C because
// underneath it uses Arbiter
// `let ens160 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x52);`: https://docs.rs/rtic-sync/latest/rtic_sync/arbiter/i2c/index.html
let eco2 = cx.local.ens160.eco2().await;

// To fix this we should drop `i2c` so access to I2C would be released
// and other tasks doing `.access()` could then continue
drop(i2c);
let eco2 = cx.local.ens160.eco2().await;

// or if you wanna use scope
{
    let mut i2c = i2c.access().await;
    asensor::reset(&mut I2c).await. 
} // i2c will be dropped automatically when scope ends

let eco2 = cx.local.ens160.eco2().await;

Notice how Arbiter I2C also uses .access(): https://github.com/rtic-rs/rtic/blob/master/rtic-sync/src/arbiter.rs#L361

There seems to be multiple issues with your projects, for example async traits support for ens160 can be enabled as: ens160 = { version= "0.6", default-features = false, features = ["async"]}.

In this example embassy stm32 I2C is used: https://docs.rs/rtic-sync/latest/rtic_sync/arbiter/i2c/index.html. That is why there is static lifetime in there. For other HALs it is defined differently. Probably that is the reason why you get these lifetime errors.

Could you add your full example to github so it can be cloned and built.

andresv commented 9 months ago

Oh stm32f4xx-hal does not support async: https://github.com/stm32-rs/stm32f4xx-hal/blob/master/Cargo.toml#L69. I suggest to use embassy-stm32 https://github.com/embassy-rs/embassy/blob/main/examples/stm32f4/src/bin/i2c_async.rs. It has the best async drivers and they also work with rtic.

pdgilbert commented 9 months ago

I added a repo with the example here: https://github.com/pdgilbert/ens160x2_arbit . I have been planning to play with embassy sometime. First I was hoping I could do a fairly straight-forward conversion from shared-bus to embedded-hal-bus for some examples. It does not seem to be as straight-forward as I was hoping.

pdgilbert commented 7 months ago

I have been trying to compile the Arbiter example indicated above by @perlindgren, which is the usage example in rtic-rs/rtic/rtic-sync/src/arbiter.rs. It appears to be untested. I am trying with both stm32f4xx_hal and stm32g4xx_hal. I had to fix some typos and make adjustments for the different hals.

There is a problem with an unsatisfied trait bound for &mut Ens160<ArbiterDevice<'static, I2c<I2C1>>> . I have tried with both release and git versions of rtic-sync. Below are results from the git version using stm32f4xx_hal.

Click to expand code ``` // I2C bus sharing using [`Arbiter`] // // An Example how to use it in RTIC application: #![deny(unsafe_code)] #![no_std] #![no_main] #![feature(type_alias_impl_trait)] #[cfg(debug_assertions)] use panic_semihosting as _; #[cfg(not(debug_assertions))] use panic_halt as _; use rtic::app; #[cfg_attr(feature = "stm32f4xx", app(device = stm32f4xx_hal::pac, dispatchers = [TIM2, TIM3]))] mod app { use core::mem::MaybeUninit; use rtic_sync::arbiter::{i2c::ArbiterDevice, Arbiter}; // Instantiate an Arbiter with a static lifetime. static ARBITER: Arbiter = Arbiter::new(32); use ens160::{Ens160, AirQualityIndex, ECo2}; pub use stm32f4xx_hal::{ pac::{I2C1, TIM2, TIM5}, i2c::I2c, gpio::GpioExt, rcc::{Clocks, RccExt}, timer::TimerExt, prelude::*, }; pub type I2c1Type = I2c; #[shared] struct Shared {} #[local] struct Local { ens160: Ens160>, } #[init(local = [ i2c_arbiter: MaybeUninit> = MaybeUninit::uninit(), ])] fn init(cx: init::Context) -> (Shared, Local) { let rcc = cx.device.RCC.constrain(); let clocks = rcc.cfgr.freeze(); let gpiob = cx.device.GPIOB.split(); let scl = gpiob.pb8.into_alternate_open_drain(); let sda = gpiob.pb9.into_alternate_open_drain(); let i2c = I2c::new(cx.device.I2C1, (scl, sda), 400.kHz(), &clocks); let i2c_arbiter = cx.local.i2c_arbiter.write(Arbiter::new(i2c)); let ens160 = Ens160::new(ArbiterDevice::new(i2c_arbiter), 0x52); i2c_sensors::spawn(i2c_arbiter).ok(); (Shared {}, Local { ens160 }) } #[task(local = [ens160])] async fn i2c_sensors(cx: i2c_sensors::Context, i2c: &'static Arbiter) { //async fn i2c_sensors(cx: i2c_sensors::Context, i2c: &'static Arbiter>) { loop { // Read ENS160 sensor. let eco2 = cx.local.ens160.eco2().await; } } } ```
Click to expand compiling error ``` $ cargo build --no-default-features --target thumbv7em-none-eabihf --features stm32f411,stm32f4xx --example ens160_f4 Compiling rust-integration-testing-of-examples v0.3.0 (dev-testing) warning: unused imports: `AirQualityIndex`, `ECo2` --> examples/ens160_f4.rs:27:26 | 27 | use ens160::{Ens160, AirQualityIndex, ECo2}; | ^^^^^^^^^^^^^^^ ^^^^ | = note: `#[warn(unused_imports)]` on by default error[E0599]: the method `eco2` exists for mutable reference `&mut Ens160>>`, but its trait bounds were not satisfied --> examples/ens160_f4.rs:75:40 | 75 | let eco2 = cx.local.ens160.eco2().await; | ^^^^ method cannot be called on `&mut Ens160>>` due to unsatisfied trait bounds | ::: /home/paul/.cargo/git/checkouts/rtic-98d87eafbe677b11/22ac33a/rtic-sync/src/arbiter.rs:330:5 | 330 | pub struct ArbiterDevice<'a, BUS> { | --------------------------------- doesn't satisfy `_: I2c` | = note: the full type name has been written to 'dev-testing/target/thumbv7em-none-eabihf/debug/examples/ens160_f4-64d1daf9f661405a.long-type-16809872599107602039.txt' = note: the following trait bounds were not satisfied: `rtic_sync::arbiter::i2c::ArbiterDevice<'static, stm32f4xx_hal::i2c::I2c>: stm32f4xx_hal::embedded_hal::i2c::I2c` For more information about this error, try `rustc --explain E0599`. warning: `rust-integration-testing-of-examples` (example "ens160_f4") generated 1 warning error: could not compile `rust-integration-testing-of-examples` (example "ens160_f4") due to 1 previous error; 1 warning emitted ```

Suggestions appreciated. Also, a pointer to an example that is tested would be nice.

andresv commented 7 months ago

stm32f4xx_hal and stm32g4xx_hal do not support https://lib.rs/crates/embedded-hal-async traits which are needed by Arbiter.

embassy-stm32 is preferable async HAL for STM32.

andresv commented 7 months ago
pdgilbert commented 7 months ago

Thank you @andresv . Your demo is helpful and I will explore embassy more sometime soon. My more immediate objective is to try and get some stm32f4xx_hal and stm32g4xx_hal examples working again after the switch to embedded-hal v1.0. So switching to embassy would be a switch away from what I am trying to test. If it really is not possible to share the i2c bus using rtic and stm32f4xx_hal or stm32g4xx_hal then I may be switching to embassy-stm32 soon.

I will close this issue now because the discussion has shifted a long way from the original subject.

andresv commented 7 months ago

Yes stm32f4xx_hal and stm32g4xx_hal would not work because they do not support async. Nice thing about embassy-stm32 is that you can use it with RTIC v2 as can be seen from my demo. It is just an async HAL that works both with RTIC and embassy executor and if somebody comes up with another async executor there is high chance that it would also work in there.

embassy-stm32 supports ALL* STM32 MCUs and API is the same for all of them. With stm32f4xx_hal and stm32g4xx_hal you always have to figure out what this particular STM32 HAL is doing little bit differently. Like they all have different feature flags and little bit different API and fixes from one HAL that are applicable to others are not applied because of different maintainers and what not. In this sense embassy-stm32 is very nice to work with and this is the way to go. I think because of its success other HAL maintainers have not bothered to add async support to their HALs because why duplicate efforts and fragment ecosystem.

ziguana commented 7 months ago

Thank you very much for the summary. I was waiting for the stm32f1xx_hal to implement the 1.0 traits, but will try this instead.