diff --git a/README.md b/README.md index e237bb3..b21e4a0 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,19 @@ -# embedded-rust-template -Template repository for Embedded Rust development +# PCAL6416A Rust Device Driver + +A `#[no_std]` platform-agnostic driver for the [PCAL6416A](https://www.nxp.com/docs/en/data-sheet/PCAL6416A.pdf) IO Expander + +## MSRV + +Currently, rust `1.81` and up is supported. + +## License + +Licensed under the terms of the [MIT license](http://opensource.org/licenses/MIT). + +## Contribution + +Unless you explicitly state otherwise, any contribution submitted for +inclusion in the work by you shall be licensed under the terms of the +MIT license. + +License: MIT \ No newline at end of file diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2677142 --- /dev/null +++ b/build.rs @@ -0,0 +1,4 @@ +#![allow(missing_docs)] +fn main() { + println!("cargo:rebuild-if-changed=device.yaml"); +} diff --git a/cargo.toml b/cargo.toml new file mode 100644 index 0000000..53d8d41 --- /dev/null +++ b/cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pcal6416a" +version = "0.1.0" +repository = "https://github.com/pop-project/pcal6416a" +license = "MIT" +description = "Platform-agnostic Rust driver for the Texas Instruments BQ25773 battery charge controller." +readme = "README.md" +keywords = ["pcal6416a", "NXP", "IO Expander", "i2c", "driver", "embedded-hal-driver"] +categories = ["embedded", "hardware-support", "no-std"] +documentation = "https://docs.rs/pcal6416a" +include = [ + "/**/*.rs", + "/Cargo.toml", + "/README.md", + "/LICENSE", + "/device.yaml", +] +edition = "2021" + +[dependencies] +device-driver = { version = "=1.0.0-rc.1", default-features = false, features = ["yaml"] } +defmt = { version = "0.3", optional = true } +embedded-hal = "1.0.0" +embedded-hal-async = "1.0.0" + +[lints.rust] +unsafe_code = "forbid" +missing_docs = "deny" + + +[lints.clippy] +correctness = "forbid" +suspicious = "forbid" +perf = "forbid" +style = "forbid" +pedantic = "deny" + +[features] +defmt-03 = ["dep:defmt", "device-driver/defmt-03"] \ No newline at end of file diff --git a/device.yaml b/device.yaml new file mode 100644 index 0000000..d0c48c4 --- /dev/null +++ b/device.yaml @@ -0,0 +1,160 @@ +config: + register_address_type: u8 + default_byte_order: LE + default_bit_order: LSB0 + defmt_feature: defmt-03 + +OUTPUT_PORT0: + type: register + address: 0x02 + size_bits: 8 + reset_value: 0xFF + fields: + O0_0: + base: bool + start: 0 + description: Output Port 0 Pin 0. Host writes this bit to set the desired logic output level. + O0_1: + base: bool + start: 1 + description: Output Port 0 Pin 1. Host writes this bit to set the desired logic output level. + O0_2: + base: bool + start: 2 + description: Output Port 0 Pin 2. Host writes this bit to set the desired logic output level. + O0_3: + base: bool + start: 3 + description: Output Port 0 Pin 3. Host writes this bit to set the desired logic output level. + O0_4: + base: bool + start: 4 + description: Output Port 0 Pin 4. Host writes this bit to set the desired logic output level. + O0_5: + base: bool + start: 5 + description: Output Port 0 Pin 5. Host writes this bit to set the desired logic output level. + O0_6: + base: bool + start: 6 + description: Output Port 0 Pin 6. Host writes this bit to set the desired logic output level. + O0_7: + base: bool + start: 7 + description: Output Port 0 Pin 7. Host writes this bit to set the desired logic output level. + +OUTPUT_PORT1: + type: register + address: 0x03 + size_bits: 8 + reset_value: 0xFF + fields: + O1_0: + base: bool + start: 0 + description: Output Port 1 Pin 0. Host writes this bit to set the desired logic output level. + O1_1: + base: bool + start: 1 + description: Output Port 1 Pin 1. Host writes this bit to set the desired logic output level. + O1_2: + base: bool + start: 2 + description: Output Port 1 Pin 2. Host writes this bit to set the desired logic output level. + O1_3: + base: bool + start: 3 + description: Output Port 1 Pin 3. Host writes this bit to set the desired logic output level. + O1_4: + base: bool + start: 4 + description: Output Port 1 Pin 4. Host writes this bit to set the desired logic output level. + O1_5: + base: bool + start: 5 + description: Output Port 1 Pin 5. Host writes this bit to set the desired logic output level. + O1_6: + base: bool + start: 6 + description: Output Port 1 Pin 6. Host writes this bit to set the desired logic output level. + O1_7: + base: bool + start: 7 + description: Output Port 1 Pin 7. Host writes this bit to set the desired logic output level. + +CONFIG_PORT0: + type: register + address: 0x06 + size_bits: 8 + reset_value: 0xFF + fields: + C0_0: + base: bool + start: 0 + description: Config Port 0 Pin 0. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_1: + base: bool + start: 1 + description: Config Port 0 Pin 1. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_2: + base: bool + start: 2 + description: Config Port 0 Pin 2. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_3: + base: bool + start: 3 + description: Config Port 0 Pin 3. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_4: + base: bool + start: 4 + description: Config Port 0 Pin 4. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_5: + base: bool + start: 5 + description: Config Port 0 Pin 5. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_6: + base: bool + start: 6 + description: Config Port 0 Pin 6. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C0_7: + base: bool + start: 7 + description: Config Port 0 Pin 7. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. +CONFIG_PORT1: + type: register + address: 0x07 + size_bits: 8 + reset_value: 0xFF + fields: + C1_0: + base: bool + start: 0 + description: Config Port 1 Pin 0. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_1: + base: bool + start: 1 + description: Config Port 1 Pin 1. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_2: + base: bool + start: 2 + description: Config Port 1 Pin 2. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_3: + base: bool + start: 3 + description: Config Port 1 Pin 3. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_4: + base: bool + start: 4 + description: Config Port 1 Pin 4. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_5: + base: bool + start: 5 + description: Config Port 1 Pin 5. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_6: + base: bool + start: 6 + description: Config Port 1 Pin 6. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. + C1_7: + base: bool + start: 7 + description: Config Port 1 Pin 7. Host clears this bit to set the pin as an output. Bit is set by default and configures the pin as an input. \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9338e4a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,75 @@ +//! This is a platform-agnostic Rust driver for the NXP PCAL6416A IO Expander +//! based on the [`embedded-hal`] traits. +//! +//! [`embedded-hal`]: https://docs.rs/embedded-hal +//! +//! For further details of the device architecture and operation, please refer +//! to the official [`Datasheet`]. +//! +//! [`Datasheet`]: https://www.nxp.com/docs/en/data-sheet/PCAL6416A.pdf + +#![doc = include_str!("../README.md")] +#![no_std] +#![allow(missing_docs)] + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[cfg_attr(feature = "defmt-03", derive(defmt::Format))] +pub enum Pcal6416aError { + /// I2C bus error + I2c(E), +} +const IOEXP_ADDR: u8 = 0x20; +const LARGEST_REG_SIZE_BYTES: usize = 2; + +pub struct Pcal6416aDevice { + pub i2cbus: I2c, +} + +device_driver::create_device!( + device_name: Device, + manifest: "device.yaml" +); + +impl device_driver::AsyncRegisterInterface + for Pcal6416aDevice +{ + type Error = Pcal6416aError; + type AddressType = u8; + + async fn write_register( + &mut self, + address: Self::AddressType, + _size_bits: u32, + data: &[u8], + ) -> Result<(), Self::Error> { + assert!( + (data.len() <= LARGEST_REG_SIZE_BYTES), + "Register size too big" + ); + + // Add one byte for register address + let mut buf = [0u8; 1 + LARGEST_REG_SIZE_BYTES]; + buf[0] = address; + buf[1..=data.len()].copy_from_slice(data); + + // Because the pcal6416a has a mix of 1 byte and 2 byte registers that can be written to, + // we pass in a slice of the appropriate size so we do not accidentally write to the register at + // address + 1 when writing to a 1 byte register + self.i2cbus + .write(IOEXP_ADDR, &buf[..=data.len()]) + .await + .map_err(Pcal6416aError::I2c) + } + + async fn read_register( + &mut self, + address: Self::AddressType, + _size_bits: u32, + data: &mut [u8], + ) -> Result<(), Self::Error> { + self.i2cbus + .write_read(IOEXP_ADDR, &[address], data) + .await + .map_err(Pcal6416aError::I2c) + } +}