diff --git a/examples/pca9956b.rs b/examples/pca9956b.rs new file mode 100644 index 00000000..80c3eda5 --- /dev/null +++ b/examples/pca9956b.rs @@ -0,0 +1,79 @@ +// Copyright 2018, Piers Finlayson +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms.extern crate i2cdev; + +extern crate i2cdev; +extern crate docopt; + +#[cfg(any(target_os = "linux", target_os = "android"))] +use i2cdev::core::{I2CTransfer, I2CMessage}; +#[cfg(any(target_os = "linux", target_os = "android"))] +use i2cdev::linux::{LinuxI2CBus, LinuxI2CMessage}; + +use std::env::args; +use docopt::Docopt; + +const USAGE: &str = " +Reads registers from a PCA9956B IC via Linux i2cdev. + +Assumes the PCA9956B is using address 0x20. + +Usage: + pca9956b + pca9956b (-h | --help) + pca9956b --version + +Options: + -h --help Show this help text. + --version Show version. +"; + +const ADDR: u16 = 0x20; + +#[cfg(any(target_os = "linux", target_os = "android"))] +fn main() { + let args = Docopt::new(USAGE) + .and_then(|d| d.argv(args()).parse()) + .unwrap_or_else(|e| e.exit()); + let path = args.get_str(""); + let mut bus = match LinuxI2CBus::new(path) { + Ok(bus) => bus, + Err(_e) => { + println!("Error opening I2C Bus {} {}", path, _e); + return; + } + }; + println!("Opened I2C Bus OK: {}", path); + + // Build two I2C messages: + // 1) Write the MODE1 register address, with top bit indcating auto- + // increment should be enabled + // 2) Read 10 bytes from the current register onwards + let mut data = [0; 10]; + let mut msgs = [ + LinuxI2CMessage::write(&[0b1000_0000]).with_address(ADDR), + LinuxI2CMessage::read(&mut data).with_address(ADDR), + ]; + + // Send the messages to the kernel to process + match bus.transfer(&mut msgs) { + Ok(rc) => println!("Successful transfer call: {} messages processed", rc), + Err(_e) => { + println!("Error reading/writing {}", _e); + return; + } + } + + // Print the data read from the device. A recently reset PCA9956B should + // return: + // 0x8005000000000000ff00 + let mut output = "Result: 0x".to_string(); + for byte in &data { + output = format!("{}{:02x}", output, byte); + } + println!("{}", output); +} diff --git a/src/core.rs b/src/core.rs index 092974b8..615eacc8 100644 --- a/src/core.rs +++ b/src/core.rs @@ -111,3 +111,33 @@ pub trait I2CDevice { /// 1 to 31 bytes of data from it. fn smbus_process_block(&mut self, register: u8, values: &[u8]) -> Result, Self::Error>; } + +/// Interface to an I2C Bus from an I2C Master +/// +/// This is used when the client wants to interact directly with the bus +/// without specifying an I2C slave address up-front, either because it needs +/// to communicate with multiple addresses without creatings separate +/// I2CDevice objects, or because it wants to make used of the I2C_RDWR ioctl +/// which allows the client to send and transmit multiple sets I2C data in a +/// single operation, potentially to different I2C slave addresses. +/// +/// Typical implementations will store state with references to the bus +/// in use. The trait is based on the Linux i2cdev interface. +pub trait I2CTransfer<'a> { + type Error: Error; + type Message: I2CMessage<'a>; + + // Performs multiple serially chained I2C read/write transactions. On + // success the return code is the number of successfully executed + // transactions + fn transfer(&mut self, msgs: &'a mut [Self::Message]) -> Result; +} + +/// Read/Write I2C message +pub trait I2CMessage<'a> { + /// Read data from device + fn read(data: &'a mut [u8]) -> Self; + + /// Write data to device + fn write(data: &'a [u8]) -> Self; +} diff --git a/src/ffi.rs b/src/ffi.rs index f043d4d6..2da7bd0f 100644 --- a/src/ffi.rs +++ b/src/ffi.rs @@ -14,41 +14,22 @@ use std::mem; use std::ptr; use std::io::Cursor; use std::os::unix::prelude::*; +use std::marker::PhantomData; use byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use libc::c_int; pub type I2CError = nix::Error; -bitflags! { - struct I2CMsgFlags: u16 { - /// this is a ten bit chip address - const I2C_M_TEN = 0x0010; - /// read data, from slave to master - const I2C_M_RD = 0x0001; - /// if I2C_FUNC_PROTOCOL_MANGLING - const I2C_M_STOP = 0x8000; - /// if I2C_FUNC_NOSTART - const I2C_M_NOSTART = 0x4000; - /// if I2C_FUNC_PROTOCOL_MANGLING - const I2C_M_REV_DIR_ADDR = 0x2000; - /// if I2C_FUNC_PROTOCOL_MANGLING - const I2C_M_IGNORE_NAK = 0x1000; - /// if I2C_FUNC_PROTOCOL_MANGLING - const I2C_M_NO_RD_ACK = 0x0800; - /// length will be first received byte - const I2C_M_RECV_LEN = 0x0400; - } -} - #[repr(C)] -struct i2c_msg { +pub struct i2c_msg { /// slave address - addr: u16, + pub(crate) addr: u16, /// serialized I2CMsgFlags - flags: u16, + pub(crate) flags: u16, /// msg length - len: u16, + pub(crate) len: u16, /// pointer to msg data - buf: *mut u8, + pub(crate) buf: *const u8, } bitflags! { @@ -163,8 +144,9 @@ pub struct i2c_smbus_ioctl_data { } /// This is the structure as used in the I2C_RDWR ioctl call +// see linux/i2c-dev.h #[repr(C)] -struct i2c_rdwr_ioctl_data { +pub struct i2c_rdwr_ioctl_data { // struct i2c_msg __user *msgs; msgs: *mut i2c_msg, // __u32 nmsgs; @@ -172,14 +154,15 @@ struct i2c_rdwr_ioctl_data { } mod ioctl { - use super::{I2C_SLAVE, I2C_SMBUS}; + use super::{I2C_SLAVE, I2C_SMBUS, I2C_RDWR}; pub use super::i2c_smbus_ioctl_data; + pub use super::i2c_rdwr_ioctl_data; ioctl_write_int_bad!(set_i2c_slave_address, I2C_SLAVE); ioctl_write_ptr_bad!(i2c_smbus, I2C_SMBUS, i2c_smbus_ioctl_data); + ioctl_write_ptr_bad!(i2c_rdwr, I2C_RDWR, i2c_rdwr_ioctl_data); } - pub fn i2c_set_slave_address(fd: RawFd, slave_address: u16) -> Result<(), nix::Error> { unsafe { ioctl::set_i2c_slave_address(fd, slave_address as i32)?; @@ -429,3 +412,17 @@ pub fn i2c_smbus_process_call_block(fd: RawFd, register: u8, values: &[u8]) -> R let count = data.block[0]; Ok((&data.block[1..(count + 1) as usize]).to_vec()) } + +#[inline] +pub fn i2c_rdwr(fd: RawFd, values: &mut [i2c_msg]) -> Result { + let i2c_data = i2c_rdwr_ioctl_data { + msgs: values.as_mut_ptr(), + nmsgs: values.len() as u32, + }; + + let n; + unsafe { + n = ioctl::i2c_rdwr(fd, &i2c_data)?; + } + Ok(n as u32) +} diff --git a/src/lib.rs b/src/lib.rs index 2080255e..b42fcd41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,7 @@ //! https://www.kernel.org/doc/Documentation/i2c/dev-interface //! ```rust,no_run //! extern crate i2cdev; -//! +//! //! use std::thread; //! use std::time::Duration; //! @@ -22,7 +22,7 @@ //! use i2cdev::linux::{LinuxI2CDevice, LinuxI2CError}; //! //! const NUNCHUCK_SLAVE_ADDR: u16 = 0x52; -//! +//! //! // real code should probably not use unwrap() //! fn i2cfun() -> Result<(), LinuxI2CError> { //! let mut dev = LinuxI2CDevice::new("/dev/i2c-1", NUNCHUCK_SLAVE_ADDR)?; @@ -41,6 +41,58 @@ //! } //! } //! ``` +//! +//! ```rust,no_run +//! extern crate i2cdev; +//! +//! use std::thread; +//! use std::time::Duration; +//! +//! use i2cdev::core::*; +//! use i2cdev::linux::{LinuxI2CDevice, LinuxI2CError, LinuxI2CMessage}; +//! +//! const SLAVE_ADDR: u16 = 0x57; +//! +//! fn write_read_transaction() -> Result<(), LinuxI2CError> { +//! let mut dev = LinuxI2CDevice::new("/dev/i2c-1", SLAVE_ADDR)?; +//! +//! let mut read_data = [0; 2]; +//! let mut msgs = [ +//! LinuxI2CMessage::write(&[0x01]), +//! LinuxI2CMessage::read(&mut read_data) +//! ]; +//! dev.transfer(&mut msgs)?; +//! +//! println!("Reading: {:?}", read_data); +//! Ok(()) +//! } +//! ``` +//! +//! ```rust,no_run +//! extern crate i2cdev; +//! +//! use std::thread; +//! use std::time::Duration; +//! +//! use i2cdev::core::*; +//! use i2cdev::linux::{LinuxI2CBus, LinuxI2CError, LinuxI2CMessage}; +//! +//! const SLAVE_ADDR: u16 = 0x57; +//! +//! fn write_read_transaction_using_bus() -> Result<(), LinuxI2CError> { +//! let mut dev = LinuxI2CBus::new("/dev/i2c-1")?; +//! +//! let mut read_data = [0; 2]; +//! let mut msgs = [ +//! LinuxI2CMessage::write(&[0x01]).with_address(SLAVE_ADDR), +//! LinuxI2CMessage::read(&mut read_data).with_address(SLAVE_ADDR) +//! ]; +//! dev.transfer(&mut msgs)?; +//! +//! println!("Reading: {:?}", read_data); +//! Ok(()) +//! } +//! ``` #![crate_name = "i2cdev"] #![crate_type = "lib"] diff --git a/src/linux.rs b/src/linux.rs index 5320d6ab..f7181a9a 100644 --- a/src/linux.rs +++ b/src/linux.rs @@ -7,7 +7,7 @@ // except according to those terms. use ffi; -use core::I2CDevice; +use core::{I2CDevice, I2CTransfer}; use std::error::Error; use std::path::Path; use std::fs::File; @@ -18,11 +18,18 @@ use std::fs::OpenOptions; use std::io::prelude::*; use std::os::unix::prelude::*; +// Expose these core structs from this module +pub use core::I2CMessage; + pub struct LinuxI2CDevice { devfile: File, slave_address: u16, } +pub struct LinuxI2CBus { + devfile: File, +} + #[derive(Debug)] pub enum LinuxI2CError { Nix(nix::Error), @@ -88,6 +95,12 @@ impl AsRawFd for LinuxI2CDevice { } } +impl AsRawFd for LinuxI2CBus { + fn as_raw_fd(&self) -> RawFd { + self.devfile.as_raw_fd() + } +} + impl LinuxI2CDevice { /// Create a new I2CDevice for the specified path pub fn new>(path: P, @@ -221,3 +234,110 @@ impl I2CDevice for LinuxI2CDevice { ffi::i2c_smbus_process_call_block(self.as_raw_fd(), register, values).map_err(From::from) } } + +impl<'a> I2CTransfer<'a> for LinuxI2CDevice { + type Error = LinuxI2CError; + type Message = LinuxI2CMessage<'a>; + + /// Issue the provided sequence of I2C transactions + fn transfer(&mut self, messages: &'a mut [Self::Message]) -> Result { + for msg in messages.iter_mut() { + (*msg).addr = self.slave_address; + } + ffi::i2c_rdwr(self.as_raw_fd(), messages).map_err(From::from) + } +} + + +impl LinuxI2CBus { + /// Create a new LinuxI2CBus for the specified path + pub fn new>(path: P) + -> Result { + let file = OpenOptions::new() + .read(true) + .write(true) + .open(path)?; + let bus = LinuxI2CBus { + devfile: file, + }; + Ok(bus) + } +} + +/// Linux I2C message +pub type LinuxI2CMessage<'a> = ffi::i2c_msg; + +impl<'a> I2CTransfer<'a> for LinuxI2CBus { + type Error = LinuxI2CError; + type Message = LinuxI2CMessage<'a>; + + /// Issue the provided sequence of I2C transactions + fn transfer(&mut self, msgs: &'a mut [Self::Message]) -> Result { + ffi::i2c_rdwr(self.as_raw_fd(), msgs).map_err(From::from) + } +} + +bitflags! { + /// Various flags used by the i2c_rdwr ioctl on Linux. For details, see + /// https://www.kernel.org/doc/Documentation/i2c/i2c-protocol + /// + /// In general, these are for special cases and should not be needed + pub struct I2CMessageFlags: u16 { + /// Use ten bit addressing on this message + const TEN_BIT_ADDRESS = 0x0010; + /// Read data, from slave to master + const READ = 0x0001; + /// Force an I2C stop condition on this message + const STOP = 0x8000; + /// Avoid sending an I2C start condition on this message + const NO_START = 0x4000; + /// If you need to invert a 'read' command bit to a 'write' + const INVERT_COMMAND = 0x2000; + /// Force this message to ignore I2C negative acknowlegements + const IGNORE_NACK = 0x1000; + /// Force message to ignore acknowledgement + const IGNORE_ACK = 0x0800; + /// Allow the client to specify how many bytes it will send + const USE_RECEIVE_LENGTH = 0x0400; + } +} + +impl<'a> I2CMessage<'a> for LinuxI2CMessage<'a> { + fn read(data: &'a mut [u8]) -> LinuxI2CMessage { + Self { + addr: 0, // will be filled later + flags: I2CMessageFlags::READ.bits(), + len: data.len() as u16, + buf: data.as_ptr(), + } + } + + fn write(data: &'a [u8]) -> LinuxI2CMessage { + Self { + addr: 0, // will be filled later + flags: I2CMessageFlags::empty().bits(), + len: data.len() as u16, + buf: data.as_ptr(), + } + } +} + +impl<'a> LinuxI2CMessage<'a> { + pub fn with_address(self, slave_address: u16) -> Self { + Self { + addr: slave_address, + flags: self.flags, + len: self.len, + buf: self.buf, + } + } + + pub fn with_flags(self, flags: I2CMessageFlags) -> Self { + Self { + addr: self.addr, + flags: flags.bits(), + len: self.len, + buf: self.buf, + } + } +} diff --git a/src/mock.rs b/src/mock.rs index 54b666d0..9498839c 100644 --- a/src/mock.rs +++ b/src/mock.rs @@ -5,7 +5,7 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -use core::I2CDevice; +use core::{I2CDevice, I2CMessage, I2CTransfer}; use std::io; pub type I2CResult = io::Result; @@ -100,3 +100,46 @@ impl I2CDevice for MockI2CDevice { unimplemented!() } } + +#[derive(Debug)] +enum MessageType<'a> { + Write(&'a [u8]), + Read(&'a mut [u8]), +} + +pub struct MockI2CMessage<'a> { + msg_type: MessageType<'a> +} + +impl<'a> I2CMessage<'a> for MockI2CMessage<'a> { + fn read(data: &'a mut [u8]) -> Self { + Self { + msg_type: MessageType::Read(data), + } + } + + /// Write data to device + fn write(data: &'a [u8]) -> Self { + Self { + msg_type: MessageType::Write(data), + } + } +} + +impl<'a> I2CTransfer<'a> for MockI2CDevice +where MockI2CDevice: I2CDevice { + type Error = io::Error; + type Message = MockI2CMessage<'a>; + + /// Issue the provided sequence of I2C transactions + fn transfer(&mut self, messages: &'a mut [Self::Message]) -> Result { + for msg in messages.iter_mut() { + match &mut msg.msg_type { + MessageType::Read(data) => self.read(data)?, + MessageType::Write(data) => self.write(data)?, + } + } + Ok(messages.len() as u32) + } +} +