Skip to content

Commit

Permalink
Merge pull request #1 from FloppyDisck/initial_impl
Browse files Browse the repository at this point in the history
Initial implementation
  • Loading branch information
FloppyDisck authored Feb 24, 2023
2 parents 25e4fb3 + fe30945 commit f9dcafc
Show file tree
Hide file tree
Showing 4 changed files with 253 additions and 1 deletion.
23 changes: 23 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
[package]
name = "i2c-multiplexer"
description = "An I2C Multiplexer library that supports the PCA9546 and TCA9546A chips"
version = "0.1.0"
edition = "2021"
license = "MIT"
repository = "https://github.com/FloppyDisck/i2c-multiplexer"
readme = "README.md"
keywords = ["embedded", "multiplexer", "PCA9546", "TCA9546A"]
categories = ["embedded"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
embedded-svc = "0.22.0"
embedded-hal = "0.2.7"

crc = "3.0.0"
thiserror = "1.0.38"

[dev-dependencies]
embedded-hal-mock = "0.9.0"
rstest = "0.16.0"
43 changes: 42 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,43 @@
# i2c-multiplexer
# I2C-Multiplexer   [![Build Status]][actions] [![Latest Version]][crates.io]
[Build Status]: https://img.shields.io/github/actions/workflow/status/FloppyDisck/i2c-multiplexer/rust.yml?branch=main
[actions]: https://github.com/FloppyDisck/i2c-multiplexer/actions?query=branch%3Amain
[Latest Version]: https://img.shields.io/crates/v/i2c-multiplexer.svg
[crates.io]: https://crates.io/crates/i2c-multiplexer
An I2C Multiplexer library that supports the PCA9546 and TCA9546A chips

---

## Usage
The sensor is initialized
```rust
use i2c_multiplexer::prelude::*;

fn main() -> Result<()> {
// Disable all ports and only enable port 0
Multiplexer::new(i2c).with_ports_disabled()?.set_port(0, true)?;
}
```

## Changing Address
```rust
use i2c_multiplexer::prelude::*;

fn main() -> Result<()> {
// Manually set the address
Multiplexer::new(i2c).with_address(0x72);

// Or set it according to the selected hardware pins
// This uses A0 which means the address is 0x71
Multiplexer::new(i2c).with_address_pins(true, false, false);
}
```

## Setting multiple ports
```rust
use i2c_multiplexer::prelude::*;

fn main() -> Result<()> {
// Manually set the ports 0,2 to enabled and 1,3 to disabled
Multiplexer::new(i2c).with_ports([true, false, true, false])?;
}
```
13 changes: 13 additions & 0 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use thiserror::Error;

pub type Result<T> = std::result::Result<T, MultiplexerError>;

#[derive(Error, Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
pub enum MultiplexerError {
#[error("Write Read I2C Error")]
WriteReadI2CError,
#[error("Write I2C Error")]
WriteI2CError,
#[error("Incorrect port supplies")]
PortError,
}
175 changes: 175 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
pub mod error;

use embedded_hal::blocking::i2c;
use error::{MultiplexerError, Result};

pub mod prelude {
pub use crate::{error::MultiplexerError, Multiplexer, PortState};
}

#[derive(Copy, Clone, Debug)]
pub enum PortState {
Enabled,
Disabled,
}

impl From<bool> for PortState {
fn from(value: bool) -> Self {
match value {
true => PortState::Enabled,
false => PortState::Disabled,
}
}
}

#[derive(Copy, Clone, Debug)]
pub struct Multiplexer<I2C: 'static + Send + Sync> {
i2c: I2C,
address: u8,
state: [bool; 4],
}

impl<I2C> Multiplexer<I2C>
where
I2C: i2c::WriteRead + i2c::Write + Send + Sync,
{
pub fn new(i2c: I2C) -> Self {
Self {
i2c,
address: 0x70,
state: [false; 4],
}
}

/// Sets the address according to the enabled hardware settings
pub fn with_address_pins(mut self, a0: bool, a1: bool, a2: bool) -> Self {
self.address = 0b1110_0000;
if a0 {
self.address |= 0b0000_0001;
}
if a1 {
self.address |= 0b0000_0010;
}
if a2 {
self.address |= 0b0000_0100;
}
self
}

/// Sets the address
pub fn with_address(mut self, address: u8) -> Self {
self.address = address;
self
}

fn port_code(states: [bool; 4]) -> u8 {
let mut code = 0;
if states[0] {
code |= 0b000_0001;
}
if states[1] {
code |= 0b000_0010;
}
if states[2] {
code |= 0b000_0100;
}
if states[3] {
code |= 0b000_1000;
}

code
}
}

impl<I2C> Multiplexer<I2C>
where
I2C: i2c::WriteRead + i2c::Write + Send + Sync,
{
/// Disables all ports
pub fn with_ports_disabled(self) -> Result<Self> {
self.with_ports([false; 4])
}

/// Disables all ports
pub fn set_ports_disabled(mut self) -> Result<()> {
self.set_ports([false; 4])
}

/// Enables all ports
pub fn with_ports_enabled(self) -> Result<Self> {
self.with_ports([true; 4])
}

/// Enables all ports
pub fn set_ports_enabled(mut self) -> Result<()> {
self.set_ports([true; 4])
}

/// Enables / Disables the selected port
pub fn set_port(&mut self, port: u8, state: impl Into<bool>) -> Result<()> {
if port >= 4 {
return Err(MultiplexerError::PortError);
}

self.state[port as usize] = state.into();

let code = Self::port_code(self.state);

self.i2c_write(&[code])
}

/// Sets the selected port
pub fn with_port(mut self, port: u8, state: impl Into<bool>) -> Result<Self> {
self.set_port(port, state.into())?;
Ok(self)
}

/// Enables / Disables the selected ports
pub fn set_ports(&mut self, ports: [bool; 4]) -> Result<()> {
let code = Self::port_code(ports);
self.i2c_write(&[code])
}

/// Enables / Disables the selected ports
pub fn with_ports(mut self, ports: [bool; 4]) -> Result<Self> {
self.set_ports(ports)?;
Ok(self)
}

fn i2c_write(&mut self, bytes: &[u8]) -> Result<()> {
match self.i2c.write(self.address, bytes) {
Ok(res) => Ok(res),
Err(_) => Err(MultiplexerError::WriteI2CError),
}
}
}

#[cfg(test)]
mod test {
use crate::prelude::*;
use embedded_hal_mock::i2c::Mock;
use rstest::*;

#[rstest]
#[case([true;4], 0b0000_1111)]
#[case([false;4], 0b0000_0000)]
#[case([true, false, true, false], 0b0000_0101)]
fn setup_ports(#[case] ports: [bool; 4], #[case] result: u8) {
assert_eq!(Multiplexer::<Mock>::port_code(ports), result)
}

#[rstest]
#[case([true;3], 0b1110_0111)]
#[case([false;3], 0b1110_0000)]
#[case([true, false, false], 0b1110_0001)]
#[case([false, true, false], 0b1110_0010)]
#[case([true, false, true], 0b1110_0101)]
fn setup_address(#[case] addr: [bool; 3], #[case] result: u8) {
assert_eq!(
Multiplexer::new(Mock::new([]))
.with_address_pins(addr[0], addr[1], addr[2])
.address,
result
)
}
}

0 comments on commit f9dcafc

Please sign in to comment.