From a146a50841dfbacf15795bb31f07545af27e6c67 Mon Sep 17 00:00:00 2001 From: Madeleyne Vaca Date: Tue, 7 Jan 2025 19:16:29 -0800 Subject: [PATCH] Initial commit for PCAL6416A Provides support to set pin direction (input, output) and output pin level --- .github/workflows/check.yml | 5 +- Cargo.toml | 39 +++++++++ LICENSE | 2 +- README.md | 21 ++++- build.rs | 4 + deny.toml | 2 + device.yaml | 160 ++++++++++++++++++++++++++++++++++++ src/lib.rs | 70 ++++++++++++++++ 8 files changed, 299 insertions(+), 4 deletions(-) create mode 100644 Cargo.toml create mode 100644 build.rs create mode 100644 device.yaml create mode 100644 src/lib.rs diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index ed2cad5..252f554 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -204,7 +204,7 @@ jobs: fail-fast: false matrix: commit: ${{ fromJSON(needs.commit_list.outputs.commits) }} - msrv: ["1.79"] # We're relying on namespaced-features, which + msrv: ["1.81"] # We're relying on namespaced-features, which # was released in 1.60 # # We also depend on `fixed' which requires rust @@ -215,6 +215,9 @@ jobs: # # embassy-time requires 1.79 due to # collapse_debuginfo + # + # device-driver requires 1.81 for a stabilized + # core::error module name: ubuntu / ${{ matrix.msrv }} (${{ matrix.commit }}) steps: - uses: actions/checkout@v4 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/LICENSE b/LICENSE index 1a078b6..3d0f83a 100644 --- a/LICENSE +++ b/LICENSE @@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. +SOFTWARE. \ No newline at end of file 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/deny.toml b/deny.toml index 27e6a75..a197d04 100644 --- a/deny.toml +++ b/deny.toml @@ -106,6 +106,8 @@ exceptions = [ # Each entry is the crate and version constraint, and its specific allow # list #{ allow = ["Zlib"], crate = "adler32" }, + { allow = ["BSD-3-Clause"], crate = "encoding_rs" }, + { allow = ["Unicode-3.0"], crate = "unicode-ident" }, ] # Some crates don't have (easily) machine readable licensing information, 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..6207a90 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,70 @@ +//! 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) + } +}