Closed pdgilbert closed 7 months ago
You may look at:
https://docs.rs/rtic-sync/latest/rtic_sync/arbiter/i2c/index.html
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!
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?
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.
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:
If I remove the lifetime argument then errors are much worse.
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.
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.
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.
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
.
Suggestions appreciated. Also, a pointer to an example that is tested would be nice.
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.
@pdgilbert, I did a demo for F4 how to use it: https://github.com/andresv/rtic_arbiter_demo/blob/93dc3169ad993d330853f292837a32c25b773207/src/main.rs#L54-L65.
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.
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.
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.
I have several examples that share devices on an
i2c
bus. Bothrtic
(v2) versions and non-rtic
versions worked previously using crateshared-bus
. Converting toembedded-hal_1.0.0
I am also converting toembedded-hal-bus
to share thei2c
bus.With help and considerable patience from @bugadani I now have a working non-
rtic
version of an example using cratesssd1305
,ads1x1x
, andina219
. Attempting thertic
version I have run into two stumbling blocks. The first is that I cannot get theRefCellDevice
argument to live long enough. I think I need to specify that the lifetime isstatic
but I have not managed to do that correctly. I get the same error with bothstm32fxx_hal
andstm32h7xx_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( 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
andLocal
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 thesetup_from_dp
. In the non-rtic
code I can useimpl I2c
to indicate the result of thesetup_from_dp
. If I do that in thertic
code, and specify types in theShared
andLocal
structures, the compiler complainsexpected
I2c, found opaque type
. Is there a way to useimpl I2c
for the result ofsetup_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 thertic
version.