Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for multiple devices on same bus? #5

Open
sjm42 opened this issue Dec 30, 2022 · 2 comments
Open

Support for multiple devices on same bus? #5

sjm42 opened this issue Dec 30, 2022 · 2 comments

Comments

@sjm42
Copy link

sjm42 commented Dec 30, 2022

I designed an IO expander card with four PCA9555 chips with optional 8-bit ULN2308 buffers. While exploring the options to share i2c bus with several instances of either pca9535 or port-expander drivers, it struck me.

When I have something like 2 to 8 identical chips with consecutive i2c bus addresses also sharing a common _INT line, why on earth should I use something so elaborate like shared-bus, Mutex and all that?

My idea is to make my own copy of this library and add support for using a whole array of identical IO expander chips with consecutive i2c addresses. I guess it could be done backward compatible so that the array thing is created with a different constructor, new_array() perhaps, and with something like num_chips and i2c_addr_offset as parameters.

How does that sound like? I am making it for my own need anyway, but would it be beneficial to push it up to the mainstream version also?

@Rahix
Copy link
Owner

Rahix commented Jan 3, 2023

I think you may be overestimating the overhead of using shared-bus. With the default "mutex" being just a RefCell, it really amounts to a boolean check and set/reset for each access. The i2c bus operations will probably take orders of magnitude longer than these few instructions.

Also, port-expander is already using shared-bus under the hood for it's own connection sharing between all the pin objects.

That said, if a new_array() would improve usability of the library for you, I'd be all for adding it. I guess it could work somewhat like this?

impl<I2C> Pca9555<shared_bus::NullMutex<Driver<I2C>>>
where
    I2C: crate::I2cBus,
{
    pub fn new_array<const N: usize>(i2c: I2C, a0: bool, a1: bool, a2: bool) -> [Self; N] {
        Self::with_mutex_array::<N>(i2c, a0, a1, a2)
    }
}

impl<I2C, M> Pca9555<M>
where
    I2C: crate::I2cBus,
    M: shared_bus::BusMutex<Bus = Driver<I2C>>,
{
    pub fn with_mutex_array<const N: usize>(i2c: I2C, mut a0: bool, mut a1: bool, mut a2: bool) -> [Self; N] {
        core::array::from_fn(|_i| {
            let res = Self(shared_bus::BusMutex::create(Driver::new(i2c, a0, a1, a2)));
            // i'm sure the below part can be made prettier by refactoring the address passing code more
            a0 = !a0;
            let new_a1 = if !a0 { !a1 } else { a1 };
            a2 = if a1 && ! new_a1 { !a2 } else { a2 };
            a1 = new_a1;
            // TODO: panic when address overflows
        })
    }
}

Or did you have something else in mind?

@sjm42
Copy link
Author

sjm42 commented Jan 3, 2023

Well, first many thanks for your response and thought.

The Mutex thing is not at all bad per se, but this example from the readme was a showstopper for me:

let mut pca9555: port_expander::Pca9555<std::sync::Mutex<_>> =
    port_expander::Pca9555::with_mutex(i2c, true, false, false);

Well, I am developing in #![no_std] world, specifically for a Raspi pico this time. So no Mutex from std for me here.
And yes, I tried to use some Mutex implementations available, and sadly I don't remember the details any more, but
it probably had something to do with embedded-hal still being in pre-1.0 alpha and rp-pico did not get along with newer versions despite them mentioning some alpha support. To summarize, I tried and failed in using Mutex with shared-bus with no_std for a raspi pico. (edit: rp-pico board support crate, not rp-hal)

I got my project forward by making an awful special abomination of your fine library for my own purposes,
basically by adding the concept of index in order to be able to handle 8 chips with the same driver. It works for me.

Also, I would perhaps frown upon using a whole array of pca9555 drivers, but also I don't think it's that bad.
The driver would just take a few bytes of memory per chip, and I don't think my rp2040 would be that short on ram.
Probably your idea is way more elegant and means less changes to the driver. I have to think about it more.

This is my mutilated version:
https://github.com/sjm42/port-expander-multi

main...sjm42:port-expander-multi:main

Well, it's nothing to write home about and I am not proud of it, but I got things working.

To me, it looks like two things make life easier:

  1. ability to use just one driver for multiple identical chips with consecutive i2c addresses OR easily create a bunch of them as you suggested
  2. ability to read/write with u16 (also u8 would be sometimes useful) straight to the pin groups.

IMO it would be beneficial to be able to use individual pins with split() when it's relevant, and other times just use u8 or u16 at the driver API level and use the relevant pins as a group. That is something I added to the driver. It's awful but works for me.

My test program is now using my expander boards successfully:

https://github.com/sjm42/maxx-io/blob/master/src/bin/main.rs

A picture gallery of soldering the cards together:

https://photos.app.goo.gl/6y2UF4VmXHRAK2ANA

^ this is the reason for using this library in the first place. Four pca9555's per card, two cards daisy chained on a single i2c bus. A2 is selected with a jumper.

Running some diagnostic code with RGB led logic probes showing port states:

https://www.youtube.com/watch?v=wwbs2TMVYDs

I designed these cards myself with KiCAD and ordered them from https://jlcpcb.com/

They have optional places for ULN2803 chips that are 8-bit NPN darlington arrays with integrated protection diodes (for inductive loads like relays) and series base resistors for ability to use IO pins as is with no external resistors.

Datasheet: https://www.sparkfun.com/datasheets/IC/uln2803a.pdf

Gotcha: while pca9555 have internal 100k pull-ups for inputs, these ULN2803 have internal, roughly 13k pull-downs from the inputs, so with those buffers populated, pca9555 inputs will default to logic 0, not 1 anymore. Other than that, pca9555 pins can still be used as inputs if necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants