| // Copyright 2015, Paul Osborne <[email protected]> |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // http://www.apache.org/license/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| |
| //! # Spidev |
| //! |
| //! The `spidev` crate provides access to Linux spidev devices |
| //! from rust. The wrapping of the interface is pretty direct |
| //! and shouldn't cause any surprises. |
| //! |
| //! Additional information on the interface may be found in |
| //! [the kernel documentation |
| //! for spidev](https://www.kernel.org/doc/Documentation/spi/spidev). |
| //! |
| //! # Examples |
| //! |
| //! ```no_run |
| //! extern crate spidev; |
| //! use std::io; |
| //! use std::io::prelude::*; |
| //! use spidev::{Spidev, SpidevOptions, SpidevTransfer, SpiModeFlags}; |
| //! |
| //! fn create_spi() -> io::Result<Spidev> { |
| //! let mut spi = Spidev::open("/dev/spidev0.0")?; |
| //! let options = SpidevOptions::new() |
| //! .bits_per_word(8) |
| //! .max_speed_hz(20_000) |
| //! .mode(SpiModeFlags::SPI_MODE_0) |
| //! .build(); |
| //! spi.configure(&options)?; |
| //! Ok(spi) |
| //! } |
| //! |
| //! /// perform half duplex operations using Read and Write traits |
| //! fn half_duplex(spi: &mut Spidev) -> io::Result<()> { |
| //! let mut rx_buf = [0_u8; 10]; |
| //! spi.write(&[0x01, 0x02, 0x03])?; |
| //! spi.read(&mut rx_buf)?; |
| //! println!("{:?}", rx_buf); |
| //! Ok(()) |
| //! } |
| //! |
| //! /// Perform full duplex operations using Ioctl |
| //! fn full_duplex(spi: &mut Spidev) -> io::Result<()> { |
| //! // "write" transfers are also reads at the same time with |
| //! // the read having the same length as the write |
| //! let tx_buf = [0x01, 0x02, 0x03]; |
| //! let mut rx_buf = [0; 3]; |
| //! { |
| //! let mut transfer = SpidevTransfer::read_write(&tx_buf, &mut rx_buf); |
| //! spi.transfer(&mut transfer)?; |
| //! } |
| //! println!("{:?}", rx_buf); |
| //! Ok(()) |
| //! } |
| //! |
| //! fn main() { |
| //! let mut spi = create_spi().unwrap(); |
| //! println!("{:?}", half_duplex(&mut spi).unwrap()); |
| //! println!("{:?}", full_duplex(&mut spi).unwrap()); |
| //! } |
| //! ``` |
| |
| pub mod spidevioctl; |
| pub use crate::spidevioctl::SpidevTransfer; |
| |
| use bitflags::bitflags; |
| use std::fs::{File, OpenOptions}; |
| use std::io; |
| use std::io::prelude::*; |
| use std::os::unix::prelude::*; |
| use std::path::Path; |
| |
| // Constants extracted from linux/spi/spidev.h |
| bitflags! { |
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| pub struct SpiModeFlags: u32 { |
| /// Clock Phase |
| const SPI_CPHA = 0x01; |
| /// Clock Polarity |
| const SPI_CPOL = 0x02; |
| /// Chipselect Active High? |
| const SPI_CS_HIGH = 0x04; |
| /// Per-word Bits On Wire |
| const SPI_LSB_FIRST = 0x08; |
| /// SI/SO Signals Shared |
| const SPI_3WIRE = 0x10; |
| /// Loopback Mode |
| const SPI_LOOP = 0x20; |
| /// 1 dev/bus; no chipselect |
| const SPI_NO_CS = 0x40; |
| /// Slave pulls low to pause |
| const SPI_READY = 0x80; |
| |
| // Common Configurations |
| const SPI_MODE_0 = 0x00; |
| const SPI_MODE_1 = Self::SPI_CPHA.bits(); |
| const SPI_MODE_2 = Self::SPI_CPOL.bits(); |
| const SPI_MODE_3 = (Self::SPI_CPOL.bits() | Self::SPI_CPHA.bits()); |
| |
| // == Only Supported with 32-bits == |
| |
| /// Transmit with 2 wires |
| const SPI_TX_DUAL = 0x100; |
| /// Transmit with 4 wires |
| const SPI_TX_QUAD = 0x200; |
| /// Receive with 2 wires |
| const SPI_RX_DUAL = 0x400; |
| /// Receive with 4 wires |
| const SPI_RX_QUAD = 0x800; |
| } |
| } |
| |
| /// Provide high-level access to Linux Spidev Driver |
| #[derive(Debug)] |
| pub struct Spidev { |
| devfile: File, |
| } |
| |
| /// Options that control defaults for communication on a device |
| /// |
| /// Individual settings may be overridden via parameters that |
| /// are specified as part of any individual SpiTransfer when |
| /// using `transfer` or `transfer_multiple`. |
| /// |
| /// Options that are not configured with one of the builder |
| /// functions will not be modified in the kernel when |
| /// `configure` is called. |
| #[derive(Debug, Default, Clone, Copy, PartialEq)] |
| pub struct SpidevOptions { |
| pub bits_per_word: Option<u8>, |
| pub max_speed_hz: Option<u32>, |
| pub lsb_first: Option<bool>, |
| pub spi_mode: Option<SpiModeFlags>, |
| } |
| |
| impl SpidevOptions { |
| /// Create a new, empty set of options |
| pub fn new() -> SpidevOptions { |
| SpidevOptions::default() |
| } |
| |
| /// The number of bits in each SPI transfer word |
| /// |
| /// The value zero signifies eight bits. |
| pub fn bits_per_word(&mut self, bits_per_word: u8) -> &mut Self { |
| self.bits_per_word = Some(bits_per_word); |
| self |
| } |
| |
| /// The maximum SPI transfer speed, in Hz |
| /// |
| /// The controller can't necessarily assign that specific clock speed. |
| pub fn max_speed_hz(&mut self, max_speed_hz: u32) -> &mut Self { |
| self.max_speed_hz = Some(max_speed_hz); |
| self |
| } |
| |
| /// The bit justification used to transfer SPI words |
| /// |
| /// Zero indicates MSB-first; other values indicate the less common |
| /// LSB-first encoding. In both cases the specified value is |
| /// right-justified in each word, so that unused (TX) or undefined (RX) |
| /// bits are in the MSBs. |
| pub fn lsb_first(&mut self, lsb_first: bool) -> &mut Self { |
| self.lsb_first = Some(lsb_first); |
| self |
| } |
| |
| /// Set the SPI Transfer Mode |
| /// |
| /// Use the constants SPI_MODE_0..SPI_MODE_3; or if you prefer |
| /// you can combine SPI_CPOL (clock polarity, idle high iff this |
| /// is set) or SPI_CPHA (clock phase, sample on trailing edge |
| /// iff this is set) flags. |
| /// |
| /// Note that this API will always prefer to use SPI_IOC_WR_MODE |
| /// rathern than the 32-bit one to target the greatest number of |
| /// kernels. SPI_IOC_WR_MODE32 is only present in 3.15+ kernels. |
| /// SPI_IOC_WR_MODE32 will be used iff bits higher than those in |
| /// 8bits are provided (e.g. Dual/Quad Tx/Rx). |
| pub fn mode(&mut self, mode: SpiModeFlags) -> &mut Self { |
| self.spi_mode = Some(mode); |
| self |
| } |
| |
| /// Finalize and build the SpidevOptions |
| pub fn build(&self) -> Self { |
| *self |
| } |
| } |
| |
| impl Spidev { |
| /// Wrap an already opened [`File`] for use as an spidev |
| pub fn new(devfile: File) -> Self { |
| Self { devfile } |
| } |
| |
| /// Open the spidev device with the provided path |
| /// |
| /// Typically, the path will be something like `"/dev/spidev0.0"` |
| /// where the first number if the bus and the second number |
| /// is the chip select on that bus for the device being targeted. |
| pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Spidev> { |
| let devfile = OpenOptions::new() |
| .read(true) |
| .write(true) |
| .create(false) |
| .open(path)?; |
| Ok(Self::new(devfile)) |
| } |
| |
| /// Get a reference to the underlying [`File`] object |
| pub fn inner(&self) -> &File { |
| &self.devfile |
| } |
| |
| /// Consume the object and get the underlying [`File`] object |
| pub fn into_inner(self) -> File { |
| self.devfile |
| } |
| |
| /// Write the provided configuration to this device |
| pub fn configure(&mut self, options: &SpidevOptions) -> io::Result<()> { |
| // write out each present option to the device. Options |
| // that are None are left as-is, in order to reduce |
| // overhead |
| let fd = self.devfile.as_raw_fd(); |
| if let Some(bpw) = options.bits_per_word { |
| spidevioctl::set_bits_per_word(fd, bpw)?; |
| } |
| if let Some(speed) = options.max_speed_hz { |
| spidevioctl::set_max_speed_hz(fd, speed)?; |
| } |
| if let Some(lsb_first) = options.lsb_first { |
| spidevioctl::set_lsb_first(fd, lsb_first)?; |
| } |
| if let Some(spi_mode_flags) = options.spi_mode { |
| spidevioctl::set_mode(fd, spi_mode_flags)?; |
| } |
| Ok(()) |
| } |
| |
| /// Read the current configuration from this device |
| pub fn query_configuration(&self) -> io::Result<SpidevOptions> { |
| let fd = self.devfile.as_raw_fd(); |
| |
| let bpw = spidevioctl::get_bits_per_word(fd)?; |
| let speed = spidevioctl::get_max_speed_hz(fd)?; |
| let lsb_first = (spidevioctl::get_lsb_first(fd)?) != 0; |
| |
| // Try to get the mode as 32-bit (`RD_MODE32`). Older kernels may return |
| // `ENOTTY` indicating 32-bit is not supported. In that case we retry in |
| // 8-bit mode. |
| let mode_bits = spidevioctl::get_mode_u32(fd).or_else(|err| { |
| if err.raw_os_error() == Some(libc::ENOTTY) { |
| spidevioctl::get_mode(fd).map(|value| value as u32) |
| } else { |
| Err(err) |
| } |
| })?; |
| |
| let mode = SpiModeFlags::from_bits_retain(mode_bits); |
| |
| let options = SpidevOptions::new() |
| .bits_per_word(bpw) |
| .max_speed_hz(speed) |
| .lsb_first(lsb_first) |
| .mode(mode) |
| .build(); |
| |
| Ok(options) |
| } |
| |
| /// Perform a single transfer |
| pub fn transfer(&self, transfer: &mut SpidevTransfer) -> io::Result<()> { |
| spidevioctl::transfer(self.devfile.as_raw_fd(), transfer) |
| } |
| |
| /// Perform multiple transfers in a single system call to the kernel |
| /// |
| /// Chaining together multiple requests like this can reduce latency |
| /// and be used for conveniently and efficient implementing some |
| /// protocols without extra round trips back to userspace. |
| pub fn transfer_multiple(&self, transfers: &mut [SpidevTransfer]) -> io::Result<()> { |
| spidevioctl::transfer_multiple(self.devfile.as_raw_fd(), transfers) |
| } |
| } |
| |
| impl Read for Spidev { |
| fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { |
| self.devfile.read(buf) |
| } |
| } |
| |
| impl Write for Spidev { |
| fn write(&mut self, buf: &[u8]) -> io::Result<usize> { |
| self.devfile.write(buf) |
| } |
| |
| fn flush(&mut self) -> io::Result<()> { |
| self.devfile.flush() |
| } |
| } |
| |
| impl AsRawFd for Spidev { |
| fn as_raw_fd(&self) -> RawFd { |
| self.devfile.as_raw_fd() |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::{SpiModeFlags, SpidevOptions}; |
| |
| #[test] |
| fn test_spidev_options_all() { |
| let options = SpidevOptions::new() |
| .bits_per_word(8) |
| .max_speed_hz(20_000) |
| .lsb_first(false) |
| .mode(SpiModeFlags::SPI_MODE_0) |
| .build(); |
| assert_eq!(options.bits_per_word, Some(8)); |
| assert_eq!(options.max_speed_hz, Some(20_000)); |
| assert_eq!(options.lsb_first, Some(false)); |
| assert_eq!(options.spi_mode, Some(SpiModeFlags::SPI_MODE_0)); |
| } |
| |
| #[test] |
| fn test_spidev_options_some() { |
| let mut options = SpidevOptions::new(); |
| options.bits_per_word(10); |
| options.lsb_first(true); |
| assert_eq!(options.bits_per_word, Some(10)); |
| assert_eq!(options.max_speed_hz, None); |
| assert_eq!(options.lsb_first, Some(true)); |
| assert_eq!(options.spi_mode, None); |
| } |
| } |