├── rustfmt.toml ├── .gitignore ├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── src ├── gpio │ ├── gpiomem.rs │ ├── epoll.rs │ ├── hal.rs │ ├── soft_pwm.rs │ ├── hal_unproven.rs │ ├── interrupt.rs │ └── gpiomem │ │ ├── rp1.rs │ │ └── bcm.rs ├── pwm │ ├── hal.rs │ ├── hal_unproven.rs │ └── sysfs.rs ├── lib.rs ├── macros.rs ├── uart │ ├── hal.rs │ └── termios.rs ├── i2c │ ├── hal.rs │ └── ioctl.rs ├── spi │ ├── ioctl.rs │ ├── hal.rs │ └── segment.rs ├── hal.rs └── pwm.rs ├── examples ├── uart_blocking_read.rs ├── gpio_blinkled.rs ├── pwm_blinkled.rs ├── README.md ├── gpio_blinkled_signals.rs ├── gpio_multithreaded_mpsc.rs ├── gpio_shared_button_state.rs ├── gpio_multithreaded_mutex.rs ├── spi_25aa1024.rs ├── pwm_servo.rs ├── i2c_ds3231.rs ├── gpio_servo_softpwm.rs └── gpio_status.rs ├── LICENSE ├── Cargo.toml ├── CONTRIBUTING.md ├── README.md └── CHANGELOG.md /rustfmt.toml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [golemparts] 4 | -------------------------------------------------------------------------------- /src/gpio/gpiomem.rs: -------------------------------------------------------------------------------- 1 | use crate::gpio::{Bias, Level, Mode}; 2 | 3 | pub mod bcm; 4 | pub mod rp1; 5 | 6 | pub(crate) trait GpioRegisters: std::fmt::Debug + Sync + Send { 7 | fn set_high(&self, pin: u8); 8 | fn set_low(&self, pin: u8); 9 | fn level(&self, pin: u8) -> Level; 10 | fn mode(&self, pin: u8) -> Mode; 11 | fn set_mode(&self, pin: u8, mode: Mode); 12 | fn set_bias(&self, pin: u8, bias: Bias); 13 | } 14 | -------------------------------------------------------------------------------- /src/pwm/hal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "embedded-hal-0")] 2 | use super::Pwm; 3 | 4 | #[cfg(feature = "embedded-hal-0")] 5 | impl embedded_hal_0::PwmPin for Pwm { 6 | type Duty = f64; 7 | 8 | fn disable(&mut self) { 9 | let _ = Pwm::disable(self); 10 | } 11 | 12 | fn enable(&mut self) { 13 | let _ = Pwm::enable(self); 14 | } 15 | 16 | fn get_duty(&self) -> Self::Duty { 17 | self.duty_cycle().unwrap_or_default() 18 | } 19 | 20 | fn get_max_duty(&self) -> Self::Duty { 21 | 1.0 22 | } 23 | 24 | fn set_duty(&mut self, duty: Self::Duty) { 25 | let _ = self.set_duty_cycle(duty); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /examples/uart_blocking_read.rs: -------------------------------------------------------------------------------- 1 | // uart_blocking_read.rs - Blocks while waiting for incoming serial data. 2 | 3 | use std::error::Error; 4 | use std::time::Duration; 5 | 6 | use rppal::uart::{Parity, Uart}; 7 | 8 | fn main() -> Result<(), Box> { 9 | // Connect to the primary UART and configure it for 115.2 kbit/s, no 10 | // parity bit, 8 data bits and 1 stop bit. 11 | let mut uart = Uart::new(115_200, Parity::None, 8, 1)?; 12 | 13 | // Configure read() to block until at least 1 byte is received. 14 | uart.set_read_mode(1, Duration::default())?; 15 | 16 | let mut buffer = [0u8; 1]; 17 | loop { 18 | // Fill the buffer variable with any incoming data. 19 | if uart.read(&mut buffer)? > 0 { 20 | println!("Received byte: {}", buffer[0]); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/gpio_blinkled.rs: -------------------------------------------------------------------------------- 1 | // gpio_blinkled.rs - Blinks an LED in a loop. 2 | // 3 | // Remember to add a resistor of an appropriate value in series, to prevent 4 | // exceeding the maximum current rating of the GPIO pin and the LED. 5 | // 6 | // Interrupting the process by pressing Ctrl-C causes the application to exit 7 | // immediately without resetting the pin's state, so the LED might stay lit. 8 | // Check out the gpio_blinkled_signals.rs example to learn how to properly 9 | // handle incoming signals to prevent an abnormal termination. 10 | 11 | use std::error::Error; 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | use rppal::gpio::Gpio; 16 | 17 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 18 | const GPIO_LED: u8 = 23; 19 | 20 | fn main() -> Result<(), Box> { 21 | // Retrieve the GPIO pin and configure it as an output. 22 | let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output(); 23 | 24 | loop { 25 | pin.toggle(); 26 | thread::sleep(Duration::from_millis(500)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017-2025 Rene van der Meer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! RPPAL provides access to the Raspberry Pi's GPIO, I2C, PWM, SPI and UART 2 | //! peripherals through a user-friendly interface. In addition to peripheral 3 | //! access, RPPAL also offers support for USB to serial adapters. 4 | //! 5 | //! The library can be used in conjunction with a variety of platform-agnostic 6 | //! drivers through its `embedded-hal` trait implementations. Both `embedded-hal` 7 | //! v0.2.7 and v1 are supported. 8 | //! 9 | //! RPPAL requires a recent release of Raspberry Pi OS. Similar Linux distributions 10 | //! may work, but are unsupported. 11 | //! 12 | //! Both `gnu` and `musl` libc targets are supported. RPPAL is compatible with the 13 | //! Raspberry Pi A, A+, B, B+, 2B, 3A+, 3B, 3B+, 4B, 5, CM, CM 3, CM 3+, CM 4, CM 5, 400, 14 | //! Zero, Zero W and Zero 2 W. Backwards compatibility for minor revisions isn't 15 | //! guaranteed until v1. 16 | 17 | // Used by rustdoc to link other crates to rppal's docs 18 | #![doc(html_root_url = "https://docs.rs/rppal/0.22.1")] 19 | 20 | #[macro_use] 21 | mod macros; 22 | 23 | pub mod gpio; 24 | #[cfg(any( 25 | feature = "embedded-hal-0", 26 | feature = "embedded-hal", 27 | feature = "embedded-hal-nb" 28 | ))] 29 | pub mod hal; 30 | pub mod i2c; 31 | pub mod pwm; 32 | pub mod spi; 33 | pub mod system; 34 | pub mod uart; 35 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rppal" 3 | # Also update html_root_url in lib.rs 4 | version = "0.22.1" 5 | edition = "2021" 6 | rust-version = "1.60" 7 | authors = ["Rene van der Meer "] 8 | description = "Interface for the Raspberry Pi's GPIO, I2C, PWM, SPI and UART peripherals." 9 | documentation = "https://docs.rs/rppal" 10 | repository = "https://github.com/golemparts/rppal" 11 | readme = "README.md" 12 | license = "MIT" 13 | categories = ["embedded", "hardware-support"] 14 | keywords = ["raspberry", "pi", "embedded-hal", "embedded-hal-impl", "hal"] 15 | 16 | [dependencies] 17 | libc = "0.2" 18 | nb = { version = "0.1.3", optional = true } 19 | embedded-hal-0 = { version = "0.2.7", optional = true, package = "embedded-hal" } 20 | embedded-hal = { version = "1", optional = true } 21 | embedded-hal-nb = { version = "1", optional = true } 22 | void = { version = "1.0.2", optional = true } 23 | spin_sleep = { version = "1.0.0", optional = true } 24 | 25 | [dev-dependencies] 26 | simple-signal = "1.1.1" 27 | 28 | [features] 29 | default = [] 30 | embedded-hal-0 = ["dep:embedded-hal-0", "dep:void", "embedded-hal-nb", "nb"] 31 | embedded-hal = ["dep:embedded-hal", "dep:spin_sleep"] 32 | embedded-hal-nb = ["dep:embedded-hal-nb", "embedded-hal"] 33 | hal = ["embedded-hal-0", "embedded-hal", "embedded-hal-nb"] 34 | hal-unproven = ["embedded-hal-0/unproven", "hal"] 35 | -------------------------------------------------------------------------------- /examples/pwm_blinkled.rs: -------------------------------------------------------------------------------- 1 | // pwm_blinkled.rs - Blinks an LED using hardware PWM. 2 | // 3 | // Remember to add a resistor of an appropriate value in series, to prevent 4 | // exceeding the maximum current rating of the GPIO pin and the LED. 5 | // 6 | // Interrupting the process by pressing Ctrl-C causes the application to exit 7 | // immediately without disabling the PWM channel. Check out the 8 | // gpio_blinkled_signals.rs example to learn how to properly handle incoming 9 | // signals to prevent an abnormal termination. 10 | 11 | use std::error::Error; 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | use rppal::pwm::{Channel, Polarity, Pwm}; 16 | 17 | fn main() -> Result<(), Box> { 18 | // Enable PWM channel 0 (BCM GPIO 12, physical pin 32) at 2 Hz with a 25% duty cycle. 19 | let pwm = Pwm::with_frequency(Channel::Pwm0, 2.0, 0.25, Polarity::Normal, true)?; 20 | 21 | // Sleep for 2 seconds while the LED blinks. 22 | thread::sleep(Duration::from_secs(2)); 23 | 24 | // Reconfigure the PWM channel for an 8 Hz frequency, 50% duty cycle. 25 | pwm.set_frequency(8.0, 0.5)?; 26 | 27 | thread::sleep(Duration::from_secs(3)); 28 | 29 | Ok(()) 30 | 31 | // When the pwm variable goes out of scope, the PWM channel is automatically disabled. 32 | // You can manually disable the channel by calling the Pwm::disable() method. 33 | } 34 | -------------------------------------------------------------------------------- /src/pwm/hal_unproven.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use super::Pwm; 4 | 5 | #[cfg(feature = "embedded-hal-0")] 6 | impl embedded_hal_0::Pwm for Pwm { 7 | type Duty = f64; 8 | type Channel = (); 9 | type Time = Duration; 10 | 11 | /// Disables a PWM `channel`. 12 | fn disable(&mut self, _channel: Self::Channel) { 13 | let _ = Pwm::disable(self); 14 | } 15 | 16 | /// Enables a PWM `channel`. 17 | fn enable(&mut self, _channel: Self::Channel) { 18 | let _ = Pwm::enable(self); 19 | } 20 | 21 | /// Returns the current PWM period. 22 | fn get_period(&self) -> Self::Time { 23 | self.period().unwrap_or_default() 24 | } 25 | 26 | /// Returns the current duty cycle. 27 | fn get_duty(&self, _channel: Self::Channel) -> Self::Duty { 28 | self.duty_cycle().unwrap_or_default() 29 | } 30 | 31 | /// Returns the maximum duty cycle value. 32 | fn get_max_duty(&self) -> Self::Duty { 33 | 1.0 34 | } 35 | 36 | /// Sets a new duty cycle. 37 | fn set_duty(&mut self, _channel: Self::Channel, duty: Self::Duty) { 38 | let _ = self.set_duty_cycle(duty); 39 | } 40 | 41 | /// Sets a new PWM period. 42 | fn set_period

(&mut self, period: P) 43 | where 44 | P: Into, 45 | { 46 | let _ = Pwm::set_period(self, period.into()); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | include: 13 | - target: "armv7-unknown-linux-gnueabihf" 14 | linker: "arm-linux-gnueabihf-gcc" 15 | dependencies: "crossbuild-essential-armhf" 16 | - target: "aarch64-unknown-linux-gnu" 17 | linker: "aarch64-linux-gnu-gcc" 18 | dependencies: "crossbuild-essential-arm64" 19 | - target: "armv7-unknown-linux-musleabihf" 20 | linker: "arm-linux-gnueabihf-gcc" 21 | dependencies: "crossbuild-essential-armhf" 22 | - target: "aarch64-unknown-linux-musl" 23 | linker: "aarch64-linux-gnu-gcc" 24 | dependencies: "crossbuild-essential-arm64" 25 | runs-on: ubuntu-latest 26 | steps: 27 | - uses: actions/checkout@v3 28 | - name: Install target 29 | run: rustup target add ${{ matrix.target }} 30 | - name: Install dependencies 31 | run: sudo apt-get -qq install ${{ matrix.dependencies }} 32 | - name: Build (debug) 33 | run: cargo build --all-features --target ${{ matrix.target }} --config target.${{ matrix.target }}.linker=\"${{ matrix.linker }}\" 34 | - name: Build (release) 35 | run: cargo build --all-features --target ${{ matrix.target }} --config target.${{ matrix.target }}.linker=\"${{ matrix.linker }}\" --release 36 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Examples 2 | 3 | Before running any examples that interface with external components, read through the comments in the source code and take the necessary steps and precautions, where applicable, to prevent potential damage to your Raspberry Pi or other hardware. 4 | 5 | `gpio_blinkled.rs` - Blinks an LED in a loop. 6 | 7 | `gpio_blinkled_signals.rs` - Blinks an LED in a loop, while handling any incoming `SIGINT` (Ctrl + C) and `SIGTERM` signals so the pin's state can be reset before the application exits. 8 | 9 | `gpio_multithreaded_mpsc.rs` - Blinks an LED on a separate thread using an MPSC channel. 10 | 11 | `gpio_multithreaded_mutex.rs` - Blinks an LED from multiple threads. 12 | 13 | `gpio_servo_softpwm.rs` - Rotates a servo using software-based PWM. 14 | 15 | `gpio_shared_button_state.rs` - Shares a state with an input interrupt, stops the program until N event changes. 16 | 17 | `gpio_status.rs` - Retrieves the mode and logic level for each of the pins on the 26-pin or 40-pin GPIO header, and displays the results in an ASCII table. 18 | 19 | `i2c_ds3231.rs` - Sets and retrieves the time on a Maxim Integrated DS3231 RTC using I2C. 20 | 21 | `pwm_blinkled.rs` - Blinks an LED using hardware PWM. 22 | 23 | `pwm_servo.rs` - Rotates a servo using hardware PWM. 24 | 25 | `spi_25aa1024.rs` - Transfers data to a Microchip 25AA1024 serial EEPROM using SPI. 26 | 27 | `uart_blocking_read.rs` - Blocks while waiting for incoming serial data. 28 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! parse_retval { 2 | ($retval:expr) => {{ 3 | let retval = $retval; 4 | 5 | if retval == -1 { 6 | Err(std::io::Error::last_os_error()) 7 | } else { 8 | Ok(retval) 9 | } 10 | }}; 11 | } 12 | 13 | // Initialize an array with a type that doesn't implement Copy 14 | macro_rules! init_array { 15 | ($val:expr, $len:expr) => {{ 16 | // Based on https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element 17 | 18 | use std::mem::{self, MaybeUninit}; 19 | 20 | // Create an uninitialized array of `MaybeUninit`. The `assume_init` is 21 | // safe because the type we are claiming to have initialized here is a 22 | // bunch of `MaybeUninit`s, which do not require initialization. 23 | let mut data: [MaybeUninit<_>; $len] = unsafe { MaybeUninit::uninit().assume_init() }; 24 | 25 | // Dropping a `MaybeUninit` does nothing. Thus using raw pointer 26 | // assignment instead of `ptr::write` does not cause the old 27 | // uninitialized value to be dropped. Also if there is a panic during 28 | // this loop, we have a memory leak, but there is no memory safety 29 | // issue. 30 | for elem in &mut data[..] { 31 | *elem = MaybeUninit::new($val); 32 | } 33 | 34 | // Everything is initialized. Transmute the array to the 35 | // initialized type. 36 | unsafe { mem::transmute::<_, [_; $len]>(data) } 37 | }}; 38 | } 39 | -------------------------------------------------------------------------------- /examples/gpio_blinkled_signals.rs: -------------------------------------------------------------------------------- 1 | // gpio_blinkled_signals.rs - Blinks an LED in a loop, while handling any 2 | // incoming SIGINT (Ctrl-C) and SIGTERM signals so the pin's state can be 3 | // reset before the application exits. 4 | // 5 | // Remember to add a resistor of an appropriate value in series, to prevent 6 | // exceeding the maximum current rating of the GPIO pin and the LED. 7 | 8 | use std::error::Error; 9 | use std::sync::atomic::{AtomicBool, Ordering}; 10 | use std::sync::Arc; 11 | use std::thread; 12 | use std::time::Duration; 13 | 14 | // The simple-signal crate is used to handle incoming signals. 15 | use simple_signal::{self, Signal}; 16 | 17 | use rppal::gpio::Gpio; 18 | 19 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 20 | const GPIO_LED: u8 = 23; 21 | 22 | fn main() -> Result<(), Box> { 23 | // Retrieve the GPIO pin and configure it as an output. 24 | let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output(); 25 | 26 | let running = Arc::new(AtomicBool::new(true)); 27 | 28 | // When a SIGINT (Ctrl-C) or SIGTERM signal is caught, atomically set running to false. 29 | simple_signal::set_handler(&[Signal::Int, Signal::Term], { 30 | let running = running.clone(); 31 | move |_| { 32 | running.store(false, Ordering::SeqCst); 33 | } 34 | }); 35 | 36 | // Blink the LED until running is set to false. 37 | while running.load(Ordering::SeqCst) { 38 | pin.toggle(); 39 | thread::sleep(Duration::from_millis(500)); 40 | } 41 | 42 | // After we're done blinking, turn the LED off. 43 | pin.set_low(); 44 | 45 | Ok(()) 46 | 47 | // When the pin variable goes out of scope, the GPIO pin mode is automatically reset 48 | // to its original value, provided reset_on_drop is set to true (default). 49 | } 50 | -------------------------------------------------------------------------------- /examples/gpio_multithreaded_mpsc.rs: -------------------------------------------------------------------------------- 1 | // gpio_multithreaded_mpsc.rs - Blinks an LED on a separate thread using an 2 | // MPSC channel. 3 | // 4 | // Remember to add a resistor of an appropriate value in series, to prevent 5 | // exceeding the maximum current rating of the GPIO pin and the LED. 6 | 7 | use std::error::Error; 8 | use std::sync::mpsc::channel; 9 | use std::thread; 10 | use std::time::Duration; 11 | 12 | use rppal::gpio::Gpio; 13 | 14 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 15 | const GPIO_LED: u8 = 23; 16 | 17 | fn main() -> Result<(), Box> { 18 | // Construct an asynchronous channel. Sender can be cloned if it needs to be shared with other threads. 19 | let (sender, receiver) = channel(); 20 | 21 | let led_thread = thread::spawn(move || -> Result<(), rppal::gpio::Error> { 22 | // Retrieve the GPIO pin and configure it as an output. 23 | let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output_low(); 24 | 25 | // Wait for an incoming message. Loop until a None is received. 26 | while let Some(count) = receiver.recv().unwrap() { 27 | println!("Blinking the LED {} times.", count); 28 | for _ in 0u8..count { 29 | pin.set_high(); 30 | thread::sleep(Duration::from_millis(250)); 31 | pin.set_low(); 32 | thread::sleep(Duration::from_millis(250)); 33 | } 34 | } 35 | 36 | Ok(()) 37 | }); 38 | 39 | // Request 3 blinks. We're using an asynchronous channel, so send() returns immediately. 40 | sender.send(Some(3))?; 41 | 42 | // Sending None terminates the while loop on the LED thread. 43 | sender.send(None)?; 44 | 45 | // Wait until the LED thread has finished executing. 46 | led_thread.join().unwrap()?; 47 | 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /src/uart/hal.rs: -------------------------------------------------------------------------------- 1 | #[cfg(any(feature = "embedded-hal-0", feature = "embedded-hal-nb"))] 2 | use super::{Error, Queue, Uart}; 3 | 4 | #[cfg(feature = "embedded-hal-nb")] 5 | impl embedded_hal_nb::serial::ErrorType for Uart { 6 | type Error = Error; 7 | } 8 | 9 | #[cfg(feature = "embedded-hal-nb")] 10 | impl embedded_hal_nb::serial::Error for Error { 11 | fn kind(&self) -> embedded_hal_nb::serial::ErrorKind { 12 | embedded_hal_nb::serial::ErrorKind::Other 13 | } 14 | } 15 | 16 | #[cfg(feature = "embedded-hal-nb")] 17 | impl embedded_hal_nb::serial::Read for Uart { 18 | fn read(&mut self) -> embedded_hal_nb::nb::Result { 19 | let mut buffer = [0u8; 1]; 20 | if Uart::read(self, &mut buffer)? == 0 { 21 | Err(embedded_hal_nb::nb::Error::WouldBlock) 22 | } else { 23 | Ok(buffer[0]) 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "embedded-hal-0")] 29 | impl embedded_hal_0::serial::Read for Uart { 30 | type Error = Error; 31 | 32 | fn read(&mut self) -> nb::Result { 33 | embedded_hal_nb::serial::Read::read(self) 34 | } 35 | } 36 | 37 | #[cfg(feature = "embedded-hal-nb")] 38 | impl embedded_hal_nb::serial::Write for Uart { 39 | fn write(&mut self, word: u8) -> embedded_hal_nb::nb::Result<(), Self::Error> { 40 | if Uart::write(self, &[word])? == 0 { 41 | Err(embedded_hal_nb::nb::Error::WouldBlock) 42 | } else { 43 | Ok(()) 44 | } 45 | } 46 | 47 | fn flush(&mut self) -> embedded_hal_nb::nb::Result<(), Self::Error> { 48 | Uart::flush(self, Queue::Output)?; 49 | 50 | Ok(()) 51 | } 52 | } 53 | 54 | #[cfg(feature = "embedded-hal-0")] 55 | impl embedded_hal_0::serial::Write for Uart { 56 | type Error = Error; 57 | 58 | fn write(&mut self, word: u8) -> nb::Result<(), Self::Error> { 59 | embedded_hal_nb::serial::Write::write(self, word) 60 | } 61 | 62 | fn flush(&mut self) -> nb::Result<(), Self::Error> { 63 | embedded_hal_nb::serial::Write::flush(self) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/gpio_shared_button_state.rs: -------------------------------------------------------------------------------- 1 | // gpio_shared_button_state.rs - Stops the program until a certain amount of event input changes via 2 | // a non-global shared variable (that can be done using OnceCell for example). This requires a Mutex 3 | // as it goes across threads, and Arc to make sure we have the same entry everywhere. 4 | 5 | use rppal::gpio::{Event, Gpio, Trigger}; 6 | use std::error::Error; 7 | use std::sync::{Arc, Mutex}; 8 | use std::time::Duration; 9 | 10 | const INPUT_PIN_GPIO: u8 = 27; 11 | const STOP_AFTER_N_CHANGES: u8 = 5; 12 | 13 | // The function we will run upon a Trigger. 14 | fn input_callback(event: Event, my_data: Arc>) { 15 | println!("Event: {:?}", event); 16 | *my_data.lock().unwrap() += 1; 17 | } 18 | 19 | fn main() -> Result<(), Box> { 20 | // Initialize our data, in this case it's just a number. 21 | let shared_state = Arc::new(Mutex::new(0)); 22 | 23 | // Configure the input pin. 24 | let mut input_pin = Gpio::new()?.get(INPUT_PIN_GPIO)?.into_input_pulldown(); 25 | 26 | // We need to clone this as set_async_interrupt will move it, which means it can't be accessed afterwards. 27 | let shared_state_hold = shared_state.clone(); 28 | input_pin.set_async_interrupt( 29 | Trigger::FallingEdge, 30 | Some(Duration::from_millis(50)), 31 | move |event| { 32 | // Note: you could add more parameters here! 33 | input_callback(event, shared_state_hold.clone()); 34 | }, 35 | )?; 36 | 37 | // We constantly check if we have reached our number of changes. 38 | loop { 39 | if *shared_state.lock().unwrap() >= STOP_AFTER_N_CHANGES { 40 | // Reached it, exiting the program. 41 | println!("Reached {STOP_AFTER_N_CHANGES} events, exiting..."); 42 | break; 43 | } 44 | 45 | // Suppose we do some work here that takes a second. The shorter the work takes, the quicker 46 | // we will quit upon reaching our condition. 47 | println!("Still waiting..."); 48 | std::thread::sleep(Duration::from_secs(1)); 49 | } 50 | 51 | Ok(()) 52 | } 53 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributor License Agreement ("Agreement") 2 | 3 | Thank you for your interest in contributing to RPPAL (the "Project"). This Agreement states the terms under which you may contribute to the Project any software, modifications or additions, bug fixes, documentation, or any other materials that you provide (whether electronically, verbally, or in written form) to Rene van der Meer (the "Author") related to the Project (each a "Contribution"). 4 | 5 | By providing your Contribution you grant the Author a perpetual, worldwide, non-exclusive, irrevocable, no-charge, royalty-free, sublicenseable, transferable license under all of your relevant intellectual property rights (including copyright, patent, trade secret and any other rights), to use, copy, modify, prepare derivative works of, distribute, publicly perform and publicly display your Contributions on any licensing terms, including without limitation: (a) open source licenses like the MIT license; and (b) binary, proprietary, or commercial licenses. Except for the licenses granted herein, you reserve all right, title, and interest in and to the Contribution. 6 | 7 | You confirm that you are able to grant the Author these rights. You represent that you are legally entitled to grant the above license. If your employer has rights to intellectual property that you create, you represent that you have received permission to make the Contributions on behalf of that employer, or that your employer has waived such rights for the Contributions. 8 | 9 | You represent that the Contributions are your original works of authorship, and to your knowledge, no other person claims, or has the right to claim, any right in any invention or patent related to the Contributions. You also represent that you are not legally obligated, whether by entering into an agreement or otherwise, in any way that conflicts with the terms of this license. 10 | 11 | The Author acknowledges that, except as explicitly described in this Agreement, any Contribution which you provide is on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. -------------------------------------------------------------------------------- /examples/gpio_multithreaded_mutex.rs: -------------------------------------------------------------------------------- 1 | // gpio_multithreaded_mutex.rs - Blinks an LED from multiple threads. 2 | // 3 | // Remember to add a resistor of an appropriate value in series, to prevent 4 | // exceeding the maximum current rating of the GPIO pin and the LED. 5 | 6 | use std::error::Error; 7 | use std::sync::{Arc, Mutex}; 8 | use std::thread; 9 | use std::time::Duration; 10 | 11 | use rppal::gpio::Gpio; 12 | 13 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 14 | const GPIO_LED: u8 = 23; 15 | const NUM_THREADS: usize = 3; 16 | 17 | fn main() -> Result<(), Box> { 18 | // Retrieve the GPIO pin and configure it as an output. 19 | let output_pin = Arc::new(Mutex::new(Gpio::new()?.get(GPIO_LED)?.into_output_low())); 20 | 21 | // Populate a Vec with threads so we can call join() on them later. 22 | let mut threads = Vec::with_capacity(NUM_THREADS); 23 | (0..NUM_THREADS).for_each(|thread_id| { 24 | // Clone the Arc so it can be moved to the spawned thread. 25 | let output_pin_clone = Arc::clone(&output_pin); 26 | 27 | threads.push(thread::spawn(move || { 28 | // Lock the Mutex on the spawned thread to get exclusive access to the OutputPin. 29 | let mut pin = output_pin_clone.lock().unwrap(); 30 | println!("Blinking the LED from thread {}.", thread_id); 31 | pin.set_high(); 32 | thread::sleep(Duration::from_millis(250)); 33 | pin.set_low(); 34 | thread::sleep(Duration::from_millis(250)); 35 | // The MutexGuard is automatically dropped here. 36 | })); 37 | }); 38 | 39 | // Lock the Mutex on the main thread to get exclusive access to the OutputPin. 40 | let mut pin = output_pin.lock().unwrap(); 41 | println!("Blinking the LED from the main thread."); 42 | pin.set_high(); 43 | thread::sleep(Duration::from_millis(250)); 44 | pin.set_low(); 45 | thread::sleep(Duration::from_millis(250)); 46 | // Manually drop the MutexGuard so the Mutex doesn't stay locked indefinitely. 47 | drop(pin); 48 | 49 | // Wait until all threads have finished executing. 50 | threads 51 | .into_iter() 52 | .for_each(|thread| thread.join().unwrap()); 53 | 54 | Ok(()) 55 | } 56 | -------------------------------------------------------------------------------- /examples/spi_25aa1024.rs: -------------------------------------------------------------------------------- 1 | // spi_25aa1024.rs - Transfers data to a Microchip 25AA1024 serial EEPROM using SPI. 2 | 3 | use std::error::Error; 4 | 5 | use rppal::spi::{Bus, Mode, Segment, SlaveSelect, Spi}; 6 | 7 | // Instruction set. 8 | const WRITE: u8 = 0b0010; // Write data, starting at the selected address. 9 | const READ: u8 = 0b0011; // Read data, starting at the selected address. 10 | const RDSR: u8 = 0b0101; // Read the STATUS register. 11 | const WREN: u8 = 0b0110; // Set the write enable latch (enable write operations). 12 | 13 | const WIP: u8 = 1; // Write-In-Process bit mask for the STATUS register. 14 | 15 | fn main() -> Result<(), Box> { 16 | // Configure the SPI peripheral. The 24AA1024 clocks in data on the first 17 | // rising edge of the clock signal (SPI mode 0). At 3.3 V, clock speeds of up 18 | // to 10 MHz are supported. 19 | let mut spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 8_000_000, Mode::Mode0)?; 20 | 21 | // Set the write enable latch using the WREN instruction. This is required 22 | // before any data can be written. The write enable latch is automatically 23 | // reset after a WRITE instruction is successfully executed. 24 | spi.write(&[WREN])?; 25 | 26 | // Use the WRITE instruction to select memory address 0 and write 5 bytes 27 | // (1, 2, 3, 4, 5). Addresses are specified as 24-bit values, but the 7 most 28 | // significant bits are ignored. 29 | spi.write(&[WRITE, 0, 0, 0, 1, 2, 3, 4, 5])?; 30 | 31 | // Read the STATUS register by writing the RDSR instruction, and then reading 32 | // a single byte. Loop until the WIP bit is set to 0, indicating the write 33 | // operation is completed. transfer_segments() will keep the Slave Select line 34 | // active until both segments have been transferred. 35 | let mut buffer = [0u8; 1]; 36 | loop { 37 | spi.transfer_segments(&[ 38 | Segment::with_write(&[RDSR]), 39 | Segment::with_read(&mut buffer), 40 | ])?; 41 | 42 | if buffer[0] & WIP == 0 { 43 | break; 44 | } 45 | } 46 | 47 | // Use the READ instruction to select memory address 0, specified as a 24-bit 48 | // value, and then read 5 bytes. 49 | let mut buffer = [0u8; 5]; 50 | spi.transfer_segments(&[ 51 | Segment::with_write(&[READ, 0, 0, 0]), 52 | Segment::with_read(&mut buffer), 53 | ])?; 54 | 55 | println!("Bytes read: {:?}", buffer); 56 | 57 | Ok(()) 58 | } 59 | -------------------------------------------------------------------------------- /src/i2c/hal.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, I2c}; 2 | 3 | #[cfg(feature = "embedded-hal-0")] 4 | impl embedded_hal_0::blocking::i2c::Write for I2c { 5 | type Error = Error; 6 | 7 | fn write(&mut self, address: u8, bytes: &[u8]) -> Result<(), Self::Error> { 8 | embedded_hal::i2c::I2c::write(self, address, bytes) 9 | } 10 | } 11 | 12 | #[cfg(feature = "embedded-hal-0")] 13 | impl embedded_hal_0::blocking::i2c::Read for I2c { 14 | type Error = Error; 15 | 16 | fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> { 17 | embedded_hal::i2c::I2c::read(self, address, buffer) 18 | } 19 | } 20 | 21 | #[cfg(feature = "embedded-hal-0")] 22 | impl embedded_hal_0::blocking::i2c::WriteRead for I2c { 23 | type Error = Error; 24 | 25 | fn write_read( 26 | &mut self, 27 | address: u8, 28 | bytes: &[u8], 29 | buffer: &mut [u8], 30 | ) -> Result<(), Self::Error> { 31 | embedded_hal::i2c::I2c::write_read(self, address, bytes, buffer) 32 | } 33 | } 34 | 35 | #[cfg(feature = "embedded-hal")] 36 | impl embedded_hal::i2c::ErrorType for I2c { 37 | type Error = Error; 38 | } 39 | 40 | #[cfg(feature = "embedded-hal")] 41 | impl embedded_hal::i2c::Error for Error { 42 | fn kind(&self) -> embedded_hal::i2c::ErrorKind { 43 | if let Error::Io(e) = self { 44 | use std::io::ErrorKind::*; 45 | 46 | match e.kind() { 47 | /* ResourceBusy | */ InvalidData => embedded_hal::i2c::ErrorKind::Bus, 48 | WouldBlock => embedded_hal::i2c::ErrorKind::ArbitrationLoss, 49 | _ => embedded_hal::i2c::ErrorKind::Other, 50 | } 51 | } else { 52 | embedded_hal::i2c::ErrorKind::Other 53 | } 54 | } 55 | } 56 | 57 | #[cfg(feature = "embedded-hal")] 58 | impl embedded_hal::i2c::I2c for I2c { 59 | fn transaction( 60 | &mut self, 61 | address: u8, 62 | operations: &mut [embedded_hal::i2c::Operation], 63 | ) -> Result<(), Self::Error> { 64 | self.set_slave_address(u16::from(address))?; 65 | for op in operations { 66 | match op { 67 | embedded_hal::i2c::Operation::Read(buff) => { 68 | self.read(buff)?; 69 | } 70 | embedded_hal::i2c::Operation::Write(buff) => { 71 | self.write(buff)?; 72 | } 73 | } 74 | } 75 | 76 | Ok(()) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /examples/pwm_servo.rs: -------------------------------------------------------------------------------- 1 | // pwm_servo.rs - Rotates a servo using hardware PWM. 2 | // 3 | // Calibrate your servo beforehand, and change the values listed below to fall 4 | // within your servo's safe limits to prevent potential damage. Don't power the 5 | // servo directly from the Pi's GPIO header. Current spikes during power-up and 6 | // stalls could otherwise damage your Pi, or cause your Pi to spontaneously 7 | // reboot, corrupting your microSD card. If you're powering the servo using a 8 | // separate power supply, remember to connect the grounds of the Pi and the 9 | // power supply together. 10 | // 11 | // Interrupting the process by pressing Ctrl-C causes the application to exit 12 | // immediately without disabling the PWM channel. Check out the 13 | // gpio_blinkled_signals.rs example to learn how to properly handle incoming 14 | // signals to prevent an abnormal termination. 15 | 16 | use std::error::Error; 17 | use std::thread; 18 | use std::time::Duration; 19 | 20 | use rppal::pwm::{Channel, Polarity, Pwm}; 21 | 22 | // Servo configuration. Change these values based on your servo's verified safe 23 | // minimum and maximum values. 24 | // 25 | // Period: 20 ms (50 Hz). Pulse width: min. 1200 µs, neutral 1500 µs, max. 1800 µs. 26 | const PERIOD_MS: u64 = 20; 27 | const PULSE_MIN_US: u64 = 1200; 28 | const PULSE_NEUTRAL_US: u64 = 1500; 29 | const PULSE_MAX_US: u64 = 1800; 30 | 31 | fn main() -> Result<(), Box> { 32 | // // Enable PWM channel 0 (BCM GPIO 12, physical pin 32) with the specified period, 33 | // and rotate the servo by setting the pulse width to its maximum value. 34 | let pwm = Pwm::with_period( 35 | Channel::Pwm0, 36 | Duration::from_millis(PERIOD_MS), 37 | Duration::from_micros(PULSE_MAX_US), 38 | Polarity::Normal, 39 | true, 40 | )?; 41 | 42 | // Sleep for 500 ms while the servo moves into position. 43 | thread::sleep(Duration::from_millis(500)); 44 | 45 | // Rotate the servo to the opposite side. 46 | pwm.set_pulse_width(Duration::from_micros(PULSE_MIN_US))?; 47 | 48 | thread::sleep(Duration::from_millis(500)); 49 | 50 | // Rotate the servo to its neutral (center) position in small steps. 51 | for pulse in (PULSE_MIN_US..=PULSE_NEUTRAL_US).step_by(10) { 52 | pwm.set_pulse_width(Duration::from_micros(pulse))?; 53 | thread::sleep(Duration::from_millis(20)); 54 | } 55 | 56 | Ok(()) 57 | 58 | // When the pwm variable goes out of scope, the PWM channel is automatically disabled. 59 | // You can manually disable the channel by calling the Pwm::disable() method. 60 | } 61 | -------------------------------------------------------------------------------- /examples/i2c_ds3231.rs: -------------------------------------------------------------------------------- 1 | // i2c_ds3231.rs - Sets and retrieves the time on a Maxim Integrated DS3231 2 | // RTC using I2C. 3 | 4 | use std::error::Error; 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use rppal::i2c::I2c; 9 | 10 | // DS3231 I2C default slave address. 11 | const ADDR_DS3231: u16 = 0x68; 12 | 13 | // DS3231 register addresses. 14 | const REG_SECONDS: usize = 0x00; 15 | const REG_MINUTES: usize = 0x01; 16 | const REG_HOURS: usize = 0x02; 17 | 18 | // Helper functions to encode and decode binary-coded decimal (BCD) values. 19 | fn bcd2dec(bcd: u8) -> u8 { 20 | (((bcd & 0xF0) >> 4) * 10) + (bcd & 0x0F) 21 | } 22 | 23 | fn dec2bcd(dec: u8) -> u8 { 24 | ((dec / 10) << 4) | (dec % 10) 25 | } 26 | 27 | fn main() -> Result<(), Box> { 28 | let mut i2c = I2c::new()?; 29 | 30 | // Set the I2C slave address to the device we're communicating with. 31 | i2c.set_slave_address(ADDR_DS3231)?; 32 | 33 | // Set the time to 11:59:50 AM. Start at register address 0x00 (Seconds) and 34 | // write 3 bytes, overwriting the Seconds, Minutes and Hours registers. 35 | // Setting bit 6 of the Hours register indicates we're using a 12-hour 36 | // format. Leaving bit 5 unset indicates AM. 37 | i2c.block_write( 38 | REG_SECONDS as u8, 39 | &[dec2bcd(50), dec2bcd(59), dec2bcd(11) | (1 << 6)], 40 | )?; 41 | 42 | let mut reg = [0u8; 3]; 43 | loop { 44 | // Start at register address 0x00 (Seconds) and read the values of the 45 | // next 3 registers (Seconds, Minutes, Hours) into the reg variable. 46 | i2c.block_read(REG_SECONDS as u8, &mut reg)?; 47 | 48 | // Display the retrieved time in the appropriate format based on bit 6 of 49 | // the Hours register. 50 | if reg[REG_HOURS] & (1 << 6) > 0 { 51 | // 12-hour format. 52 | println!( 53 | "{:0>2}:{:0>2}:{:0>2} {}", 54 | bcd2dec(reg[REG_HOURS] & 0x1F), 55 | bcd2dec(reg[REG_MINUTES]), 56 | bcd2dec(reg[REG_SECONDS]), 57 | if reg[REG_HOURS] & (1 << 5) > 0 { 58 | "PM" 59 | } else { 60 | "AM" 61 | } 62 | ); 63 | } else { 64 | // 24-hour format. 65 | println!( 66 | "{:0>2}:{:0>2}:{:0>2}", 67 | bcd2dec(reg[REG_HOURS] & 0x3F), 68 | bcd2dec(reg[REG_MINUTES]), 69 | bcd2dec(reg[REG_SECONDS]) 70 | ); 71 | } 72 | 73 | thread::sleep(Duration::from_secs(1)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /examples/gpio_servo_softpwm.rs: -------------------------------------------------------------------------------- 1 | // gpio_servo_softpwm.rs - Rotates a servo using software-based PWM. 2 | // 3 | // Calibrate your servo beforehand, and change the values listed below to fall 4 | // within your servo's safe limits to prevent potential damage. Don't power the 5 | // servo directly from the Pi's GPIO header. Current spikes during power-up and 6 | // stalls could otherwise damage your Pi, or cause your Pi to spontaneously 7 | // reboot, corrupting your microSD card. If you're powering the servo using a 8 | // separate power supply, remember to connect the grounds of the Pi and the 9 | // power supply together. 10 | // 11 | // Software-based PWM is inherently inaccurate on a multi-threaded OS due to 12 | // scheduling/preemption. If an accurate or faster PWM signal is required, use 13 | // the hardware PWM peripheral instead. Check out the pwm_servo.rs example to 14 | // learn how to control a servo using hardware PWM. 15 | 16 | use std::error::Error; 17 | use std::thread; 18 | use std::time::Duration; 19 | 20 | use rppal::gpio::Gpio; 21 | 22 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 23 | const GPIO_PWM: u8 = 23; 24 | 25 | // Servo configuration. Change these values based on your servo's verified safe 26 | // minimum and maximum values. 27 | // 28 | // Period: 20 ms (50 Hz). Pulse width: min. 1200 µs, neutral 1500 µs, max. 1800 µs. 29 | const PERIOD_MS: u64 = 20; 30 | const PULSE_MIN_US: u64 = 1200; 31 | const PULSE_NEUTRAL_US: u64 = 1500; 32 | const PULSE_MAX_US: u64 = 1800; 33 | 34 | fn main() -> Result<(), Box> { 35 | // Retrieve the GPIO pin and configure it as an output. 36 | let mut pin = Gpio::new()?.get(GPIO_PWM)?.into_output(); 37 | 38 | // Enable software-based PWM with the specified period, and rotate the servo by 39 | // setting the pulse width to its maximum value. 40 | pin.set_pwm( 41 | Duration::from_millis(PERIOD_MS), 42 | Duration::from_micros(PULSE_MAX_US), 43 | )?; 44 | 45 | // Sleep for 500 ms while the servo moves into position. 46 | thread::sleep(Duration::from_millis(500)); 47 | 48 | // Rotate the servo to the opposite side. 49 | pin.set_pwm( 50 | Duration::from_millis(PERIOD_MS), 51 | Duration::from_micros(PULSE_MIN_US), 52 | )?; 53 | 54 | thread::sleep(Duration::from_millis(500)); 55 | 56 | // Rotate the servo to its neutral (center) position in small steps. 57 | for pulse in (PULSE_MIN_US..=PULSE_NEUTRAL_US).step_by(10) { 58 | pin.set_pwm( 59 | Duration::from_millis(PERIOD_MS), 60 | Duration::from_micros(pulse), 61 | )?; 62 | thread::sleep(Duration::from_millis(20)); 63 | } 64 | 65 | Ok(()) 66 | 67 | // When the pin variable goes out of scope, software-based PWM is automatically disabled. 68 | // You can manually disable PWM by calling the clear_pwm() method. 69 | } 70 | -------------------------------------------------------------------------------- /src/gpio/epoll.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unnecessary_cast)] 2 | #![allow(dead_code)] 3 | 4 | use std::io; 5 | use std::result; 6 | use std::time::Duration; 7 | 8 | use libc::{ 9 | self, c_int, c_void, EFD_NONBLOCK, EFD_SEMAPHORE, EPOLL_CTL_ADD, EPOLL_CTL_DEL, EPOLL_CTL_MOD, 10 | }; 11 | 12 | pub use libc::{epoll_event, EPOLLERR, EPOLLET, EPOLLIN, EPOLLPRI}; 13 | 14 | pub type Result = result::Result; 15 | 16 | // We're using EventFd to wake up another thread 17 | // that's waiting for epoll_wait() to return. 18 | #[derive(Debug)] 19 | pub struct EventFd { 20 | fd: i32, 21 | } 22 | 23 | impl EventFd { 24 | pub fn new() -> Result { 25 | Ok(EventFd { 26 | fd: parse_retval!(unsafe { libc::eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE) })?, 27 | }) 28 | } 29 | 30 | pub fn notify(&self) -> Result<()> { 31 | let buffer: u64 = 1; 32 | 33 | parse_retval!(unsafe { libc::write(self.fd, &buffer as *const u64 as *const c_void, 8) })?; 34 | 35 | Ok(()) 36 | } 37 | 38 | pub fn fd(&self) -> i32 { 39 | self.fd 40 | } 41 | } 42 | 43 | impl Drop for EventFd { 44 | fn drop(&mut self) { 45 | unsafe { 46 | libc::close(self.fd); 47 | } 48 | } 49 | } 50 | 51 | #[derive(Debug)] 52 | pub struct Epoll { 53 | fd: c_int, 54 | } 55 | 56 | impl Epoll { 57 | pub fn new() -> Result { 58 | Ok(Epoll { 59 | fd: parse_retval!(unsafe { libc::epoll_create1(0) })?, 60 | }) 61 | } 62 | 63 | pub fn add(&self, fd: i32, id: u64, event_mask: i32) -> Result<()> { 64 | let mut event = epoll_event { 65 | events: event_mask as u32, 66 | u64: id as u64, 67 | }; 68 | 69 | parse_retval!(unsafe { libc::epoll_ctl(self.fd, EPOLL_CTL_ADD, fd, &mut event) })?; 70 | 71 | Ok(()) 72 | } 73 | 74 | pub fn modify(&self, fd: i32, id: u64, event_mask: i32) -> Result<()> { 75 | let mut event = epoll_event { 76 | events: event_mask as u32, 77 | u64: id as u64, 78 | }; 79 | 80 | parse_retval!(unsafe { libc::epoll_ctl(self.fd, EPOLL_CTL_MOD, fd, &mut event) })?; 81 | 82 | Ok(()) 83 | } 84 | 85 | pub fn delete(&self, fd: i32) -> Result<()> { 86 | let mut event = epoll_event { events: 0, u64: 0 }; 87 | 88 | parse_retval!(unsafe { libc::epoll_ctl(self.fd, EPOLL_CTL_DEL, fd, &mut event) })?; 89 | 90 | Ok(()) 91 | } 92 | 93 | pub fn wait(&self, events: &mut [epoll_event], timeout: Option) -> Result { 94 | if events.is_empty() { 95 | return Ok(0); 96 | } 97 | 98 | let timeout = if let Some(duration) = timeout { 99 | (duration.as_secs() * 1_000 + u64::from(duration.subsec_millis())) as c_int 100 | } else { 101 | -1 102 | }; 103 | 104 | Ok(parse_retval!(unsafe { 105 | libc::epoll_wait(self.fd, events.as_mut_ptr(), events.len() as c_int, timeout) 106 | })? as usize) 107 | } 108 | } 109 | 110 | impl Drop for Epoll { 111 | fn drop(&mut self) { 112 | unsafe { 113 | libc::close(self.fd); 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/spi/ioctl.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::io; 4 | use std::mem; 5 | use std::result; 6 | 7 | use libc::{self, c_int, ioctl}; 8 | 9 | use super::segment::Segment; 10 | 11 | #[cfg(target_env = "gnu")] 12 | type IoctlLong = libc::c_ulong; 13 | #[cfg(target_env = "musl")] 14 | type IoctlLong = c_int; 15 | pub type Result = result::Result; 16 | 17 | const NRBITS: u8 = 8; 18 | const TYPEBITS: u8 = 8; 19 | const SIZEBITS: u8 = 14; 20 | const DIRBITS: u8 = 2; 21 | const NRSHIFT: u8 = 0; 22 | const TYPESHIFT: u8 = NRSHIFT + NRBITS; 23 | const SIZESHIFT: u8 = TYPESHIFT + TYPEBITS; 24 | const DIRSHIFT: u8 = SIZESHIFT + SIZEBITS; 25 | const NR_MESSAGE: IoctlLong = 0 << NRSHIFT; 26 | const NR_MODE: IoctlLong = 1 << NRSHIFT; 27 | const NR_LSB_FIRST: IoctlLong = 2 << NRSHIFT; 28 | const NR_BITS_PER_WORD: IoctlLong = 3 << NRSHIFT; 29 | const NR_MAX_SPEED_HZ: IoctlLong = 4 << NRSHIFT; 30 | const NR_MODE32: IoctlLong = 5 << NRSHIFT; 31 | const TYPE_SPI: IoctlLong = (b'k' as IoctlLong) << TYPESHIFT; 32 | const SIZE_U8: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; 33 | const SIZE_U32: IoctlLong = (mem::size_of::() as IoctlLong) << SIZESHIFT; 34 | const DIR_NONE: IoctlLong = 0; 35 | const DIR_WRITE: IoctlLong = 1 << DIRSHIFT; 36 | const DIR_READ: IoctlLong = 2 << DIRSHIFT; 37 | const REQ_RD_MODE: IoctlLong = DIR_READ | TYPE_SPI | NR_MODE | SIZE_U8; 38 | const REQ_RD_LSB_FIRST: IoctlLong = DIR_READ | TYPE_SPI | NR_LSB_FIRST | SIZE_U8; 39 | const REQ_RD_BITS_PER_WORD: IoctlLong = DIR_READ | TYPE_SPI | NR_BITS_PER_WORD | SIZE_U8; 40 | const REQ_RD_MAX_SPEED_HZ: IoctlLong = DIR_READ | TYPE_SPI | NR_MAX_SPEED_HZ | SIZE_U32; 41 | const REQ_RD_MODE_32: IoctlLong = DIR_READ | TYPE_SPI | NR_MODE32 | SIZE_U32; 42 | const REQ_WR_MESSAGE: IoctlLong = DIR_WRITE | TYPE_SPI | NR_MESSAGE; 43 | const REQ_WR_MODE: IoctlLong = DIR_WRITE | TYPE_SPI | NR_MODE | SIZE_U8; 44 | const REQ_WR_LSB_FIRST: IoctlLong = DIR_WRITE | TYPE_SPI | NR_LSB_FIRST | SIZE_U8; 45 | const REQ_WR_BITS_PER_WORD: IoctlLong = DIR_WRITE | TYPE_SPI | NR_BITS_PER_WORD | SIZE_U8; 46 | const REQ_WR_MAX_SPEED_HZ: IoctlLong = DIR_WRITE | TYPE_SPI | NR_MAX_SPEED_HZ | SIZE_U32; 47 | const REQ_WR_MODE_32: IoctlLong = DIR_WRITE | TYPE_SPI | NR_MODE32 | SIZE_U32; 48 | pub const MODE_CPHA: u8 = 0x01; 49 | pub const MODE_CPOL: u8 = 0x02; 50 | pub const MODE_0: u8 = 0; 51 | pub const MODE_1: u8 = MODE_CPHA; 52 | pub const MODE_2: u8 = MODE_CPOL; 53 | pub const MODE_3: u8 = MODE_CPOL | MODE_CPHA; 54 | pub const MODE_CS_HIGH: u8 = 0x04; 55 | // Set SS to active high 56 | pub const MODE_LSB_FIRST: u8 = 0x08; // Set bit order to LSB first 57 | pub const MODE_3WIRE: u8 = 0x10; // Set bidirectional mode 58 | pub const MODE_LOOP: u8 = 0x20; // Set loopback mode 59 | pub const MODE_NO_CS: u8 = 0x40; // Don't assert SS 60 | pub const MODE_READY: u8 = 0x80; // Slave sends a ready signal 61 | pub const MODE_TX_DUAL: u32 = 0x0100; // Send on 2 outgoing lines 62 | pub const MODE_TX_QUAD: u32 = 0x0200; // Send on 4 outgoing lines 63 | pub const MODE_RX_DUAL: u32 = 0x0400; // Receive on 2 incoming lines 64 | pub const MODE_RX_QUAD: u32 = 0x0800; // Receive on 4 incoming lines 65 | 66 | pub fn mode(fd: c_int, value: &mut u8) -> Result { 67 | parse_retval!(unsafe { ioctl(fd, REQ_RD_MODE, value) }) 68 | } 69 | 70 | pub fn set_mode(fd: c_int, value: u8) -> Result { 71 | parse_retval!(unsafe { ioctl(fd, REQ_WR_MODE, &value) }) 72 | } 73 | 74 | pub fn lsb_first(fd: c_int, value: &mut u8) -> Result { 75 | parse_retval!(unsafe { ioctl(fd, REQ_RD_LSB_FIRST, value) }) 76 | } 77 | 78 | pub fn set_lsb_first(fd: c_int, value: u8) -> Result { 79 | parse_retval!(unsafe { ioctl(fd, REQ_WR_LSB_FIRST, &value) }) 80 | } 81 | 82 | pub fn bits_per_word(fd: c_int, value: &mut u8) -> Result { 83 | parse_retval!(unsafe { ioctl(fd, REQ_RD_BITS_PER_WORD, value) }) 84 | } 85 | 86 | pub fn set_bits_per_word(fd: c_int, value: u8) -> Result { 87 | parse_retval!(unsafe { ioctl(fd, REQ_WR_BITS_PER_WORD, &value) }) 88 | } 89 | 90 | pub fn clock_speed(fd: c_int, value: &mut u32) -> Result { 91 | parse_retval!(unsafe { ioctl(fd, REQ_RD_MAX_SPEED_HZ, value) }) 92 | } 93 | 94 | pub fn set_clock_speed(fd: c_int, value: u32) -> Result { 95 | parse_retval!(unsafe { ioctl(fd, REQ_WR_MAX_SPEED_HZ, &value) }) 96 | } 97 | 98 | pub fn mode32(fd: c_int, value: &mut u32) -> Result { 99 | parse_retval!(unsafe { ioctl(fd, REQ_RD_MODE_32, value) }) 100 | } 101 | 102 | pub fn set_mode32(fd: c_int, value: u32) -> Result { 103 | parse_retval!(unsafe { ioctl(fd, REQ_WR_MODE_32, &value) }) 104 | } 105 | 106 | pub fn transfer(fd: c_int, segments: &[Segment<'_, '_>]) -> Result { 107 | parse_retval!(unsafe { 108 | ioctl( 109 | fd, 110 | REQ_WR_MESSAGE | ((std::mem::size_of_val(segments) as IoctlLong) << SIZESHIFT), 111 | segments, 112 | ) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /src/hal.rs: -------------------------------------------------------------------------------- 1 | //! Miscellaneous `embedded-hal` trait implementations. 2 | //! 3 | //! The `hal` module consists of a collection of `embedded-hal` trait 4 | //! implementations for traits that aren't tied to a specific peripheral. 5 | //! 6 | //! This module is only included when either the `hal` or `hal-unproven` feature 7 | //! flag is enabled. 8 | 9 | use std::time::Duration; 10 | #[cfg(feature = "embedded-hal-0")] 11 | use std::time::Instant; 12 | 13 | /// Implements the `embedded-hal` `DelayMs` and `DelayNs` traits. 14 | #[derive(Debug, Default)] 15 | pub struct Delay; 16 | 17 | impl Delay { 18 | /// Constructs a new `Delay`. 19 | pub fn new() -> Delay { 20 | Delay {} 21 | } 22 | } 23 | 24 | #[cfg(feature = "embedded-hal-0")] 25 | impl embedded_hal_0::blocking::delay::DelayMs for Delay { 26 | fn delay_ms(&mut self, ms: u8) { 27 | embedded_hal::delay::DelayNs::delay_ms(self, ms as u32); 28 | } 29 | } 30 | 31 | #[cfg(feature = "embedded-hal-0")] 32 | impl embedded_hal_0::blocking::delay::DelayMs for Delay { 33 | fn delay_ms(&mut self, ms: u16) { 34 | embedded_hal::delay::DelayNs::delay_ms(self, ms as u32); 35 | } 36 | } 37 | 38 | #[cfg(feature = "embedded-hal-0")] 39 | impl embedded_hal_0::blocking::delay::DelayMs for Delay { 40 | fn delay_ms(&mut self, ms: u32) { 41 | embedded_hal::delay::DelayNs::delay_ms(self, ms); 42 | } 43 | } 44 | 45 | #[cfg(feature = "embedded-hal-0")] 46 | impl embedded_hal_0::blocking::delay::DelayMs for Delay { 47 | fn delay_ms(&mut self, mut ms: u64) { 48 | while ms > (u32::MAX as u64) { 49 | ms -= u32::MAX as u64; 50 | embedded_hal::delay::DelayNs::delay_ms(self, u32::MAX); 51 | } 52 | 53 | embedded_hal::delay::DelayNs::delay_ms(self, ms as u32); 54 | } 55 | } 56 | 57 | #[cfg(feature = "embedded-hal-0")] 58 | impl embedded_hal_0::blocking::delay::DelayUs for Delay { 59 | fn delay_us(&mut self, us: u8) { 60 | embedded_hal::delay::DelayNs::delay_us(self, us as u32); 61 | } 62 | } 63 | 64 | #[cfg(feature = "embedded-hal-0")] 65 | impl embedded_hal_0::blocking::delay::DelayUs for Delay { 66 | fn delay_us(&mut self, us: u16) { 67 | embedded_hal::delay::DelayNs::delay_us(self, us as u32); 68 | } 69 | } 70 | 71 | #[cfg(feature = "embedded-hal")] 72 | impl embedded_hal::delay::DelayNs for Delay { 73 | fn delay_ns(&mut self, ns: u32) { 74 | spin_sleep::sleep(Duration::from_nanos(ns.into())); 75 | } 76 | 77 | fn delay_us(&mut self, us: u32) { 78 | spin_sleep::sleep(Duration::from_micros(us.into())); 79 | } 80 | 81 | fn delay_ms(&mut self, ms: u32) { 82 | spin_sleep::sleep(Duration::from_millis(ms.into())); 83 | } 84 | } 85 | 86 | #[cfg(feature = "embedded-hal-0")] 87 | impl embedded_hal_0::blocking::delay::DelayUs for Delay { 88 | fn delay_us(&mut self, us: u32) { 89 | embedded_hal::delay::DelayNs::delay_us(self, us); 90 | } 91 | } 92 | 93 | #[cfg(feature = "embedded-hal-0")] 94 | impl embedded_hal_0::blocking::delay::DelayUs for Delay { 95 | fn delay_us(&mut self, mut us: u64) { 96 | while us > (u32::MAX as u64) { 97 | us -= u32::MAX as u64; 98 | embedded_hal::delay::DelayNs::delay_us(self, u32::MAX); 99 | } 100 | 101 | embedded_hal::delay::DelayNs::delay_us(self, us as u32); 102 | } 103 | } 104 | 105 | /// Newtype wrapper for `f64`. Converts into `Duration`. 106 | pub struct Hertz(pub f64); 107 | 108 | const MICROS_PER_SEC: f64 = 1_000_000.0; 109 | 110 | impl From for Duration { 111 | fn from(item: Hertz) -> Self { 112 | if item.0 > 0.0 && item.0.is_finite() { 113 | Duration::from_micros(((1.0 / item.0) * MICROS_PER_SEC) as u64) 114 | } else { 115 | Duration::default() 116 | } 117 | } 118 | } 119 | 120 | /// Implements the `embedded-hal` `CountDown` trait. 121 | #[cfg(feature = "embedded-hal-0")] 122 | #[derive(Debug, Copy, Clone)] 123 | pub struct Timer { 124 | start: Instant, 125 | duration: Duration, 126 | } 127 | 128 | #[cfg(feature = "embedded-hal-0")] 129 | impl Timer { 130 | /// Constructs a new `Timer`. 131 | pub fn new() -> Self { 132 | Self { 133 | start: Instant::now(), 134 | duration: Duration::from_micros(0), 135 | } 136 | } 137 | } 138 | 139 | #[cfg(feature = "embedded-hal-0")] 140 | impl Default for Timer { 141 | fn default() -> Self { 142 | Timer::new() 143 | } 144 | } 145 | 146 | #[cfg(feature = "embedded-hal-0")] 147 | impl embedded_hal_0::timer::CountDown for Timer { 148 | type Time = Duration; 149 | 150 | /// Starts the timer with a `timeout`. 151 | fn start(&mut self, timeout: T) 152 | where 153 | T: Into, 154 | { 155 | self.start = Instant::now(); 156 | self.duration = timeout.into(); 157 | } 158 | 159 | /// Returns `Ok` if the timer has wrapped. 160 | fn wait(&mut self) -> nb::Result<(), void::Void> { 161 | if self.start.elapsed() >= self.duration { 162 | Ok(()) 163 | } else { 164 | Err(nb::Error::WouldBlock) 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /examples/gpio_status.rs: -------------------------------------------------------------------------------- 1 | // gpio_status.rs - Retrieves the mode and logic level for each of the pins on 2 | // the 26-pin or 40-pin GPIO header, and displays the results in an ASCII table. 3 | 4 | use std::error::Error; 5 | use std::fmt; 6 | 7 | use rppal::gpio::Gpio; 8 | use rppal::system::{DeviceInfo, Model}; 9 | 10 | enum PinType { 11 | Gpio(u8), 12 | Ground, 13 | Power3v3, 14 | Power5v, 15 | } 16 | 17 | impl fmt::Display for PinType { 18 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 19 | match *self { 20 | PinType::Gpio(pin) => write!(f, "GPIO{}", pin), 21 | PinType::Ground => write!(f, "{:<5}", "GND"), 22 | PinType::Power3v3 => write!(f, "{:<5}", "3.3 V"), 23 | PinType::Power5v => write!(f, "{:<5}", "5 V"), 24 | } 25 | } 26 | } 27 | 28 | const HEADER: [PinType; 40] = [ 29 | PinType::Power3v3, // Physical pin 1 30 | PinType::Power5v, // Physical pin 2 31 | PinType::Gpio(2), // Physical pin 3 32 | PinType::Power5v, // Physical pin 4 33 | PinType::Gpio(3), // Physical pin 5 34 | PinType::Ground, // Physical pin 6 35 | PinType::Gpio(4), // Physical pin 7 36 | PinType::Gpio(14), // Physical pin 8 37 | PinType::Ground, // Physical pin 9 38 | PinType::Gpio(15), // Physical pin 10 39 | PinType::Gpio(17), // Physical pin 11 40 | PinType::Gpio(18), // Physical pin 12 41 | PinType::Gpio(27), // Physical pin 13 42 | PinType::Ground, // Physical pin 14 43 | PinType::Gpio(22), // Physical pin 15 44 | PinType::Gpio(23), // Physical pin 16 45 | PinType::Power3v3, // Physical pin 17 46 | PinType::Gpio(24), // Physical pin 18 47 | PinType::Gpio(10), // Physical pin 19 48 | PinType::Ground, // Physical pin 20 49 | PinType::Gpio(9), // Physical pin 21 50 | PinType::Gpio(25), // Physical pin 22 51 | PinType::Gpio(11), // Physical pin 23 52 | PinType::Gpio(8), // Physical pin 24 53 | PinType::Ground, // Physical pin 25 54 | PinType::Gpio(7), // Physical pin 26 55 | PinType::Gpio(0), // Physical pin 27 56 | PinType::Gpio(1), // Physical pin 28 57 | PinType::Gpio(5), // Physical pin 29 58 | PinType::Ground, // Physical pin 30 59 | PinType::Gpio(6), // Physical pin 31 60 | PinType::Gpio(12), // Physical pin 32 61 | PinType::Gpio(13), // Physical pin 33 62 | PinType::Ground, // Physical pin 34 63 | PinType::Gpio(19), // Physical pin 35 64 | PinType::Gpio(16), // Physical pin 36 65 | PinType::Gpio(26), // Physical pin 37 66 | PinType::Gpio(20), // Physical pin 38 67 | PinType::Ground, // Physical pin 39 68 | PinType::Gpio(21), // Physical pin 40 69 | ]; 70 | 71 | const MAX_PINS_SHORT: usize = 26; 72 | const MAX_PINS_LONG: usize = 40; 73 | 74 | fn format_pin( 75 | buf: &mut String, 76 | pin: usize, 77 | gpio: impl fmt::Display, 78 | mode: impl fmt::Display, 79 | level: impl fmt::Display, 80 | ) { 81 | if pin % 2 != 0 { 82 | buf.push_str(&format!( 83 | "| {:>4} | {:<5} | {:>1} | {:>2} |", 84 | gpio, mode, level, pin 85 | )); 86 | } else { 87 | buf.push_str(&format!( 88 | " {:>2} | {:>1} | {:<5} | {:>4} |\n", 89 | pin, level, mode, gpio 90 | )); 91 | } 92 | } 93 | 94 | fn print_header(header: &[PinType]) -> Result<(), Box> { 95 | let gpio = Gpio::new()?; 96 | 97 | let mut buf = String::with_capacity(1600); 98 | 99 | buf.push_str("+------+-------+---+---------+---+-------+------+\n"); 100 | buf.push_str("| GPIO | Mode | L | Pin | L | Mode | GPIO |\n"); 101 | buf.push_str("+------+-------+---+----+----+---+-------+------+\n"); 102 | 103 | for (idx, pin_type) in header.iter().enumerate() { 104 | match pin_type { 105 | PinType::Gpio(bcm_gpio) => { 106 | // Retrieve a Pin without converting it to an InputPin, 107 | // OutputPin or IoPin, so we can check the pin's mode 108 | // and level without affecting its state. 109 | let pin = gpio.get(*bcm_gpio)?; 110 | 111 | format_pin( 112 | &mut buf, 113 | idx + 1, 114 | bcm_gpio, 115 | format!("{}", pin.mode()).to_uppercase(), 116 | pin.read() as u8, 117 | ); 118 | } 119 | _ => format_pin(&mut buf, idx + 1, "", pin_type, ""), 120 | }; 121 | } 122 | 123 | buf.push_str("+------+-------+---+----+----+---+-------+------+\n"); 124 | 125 | print!("{}", buf); 126 | 127 | Ok(()) 128 | } 129 | 130 | fn main() -> Result<(), Box> { 131 | // Identify the Pi's model, so we can print the appropriate GPIO header. 132 | match DeviceInfo::new()?.model() { 133 | Model::RaspberryPiBRev1 => { 134 | // The GPIO header on the earlier Pi models mostly overlaps with the first 26 pins of 135 | // the 40-pin header on the newer models. A few pins are switched on the Pi B Rev 1. 136 | let mut header_rev1 = HEADER; 137 | header_rev1[2] = PinType::Gpio(0); 138 | header_rev1[4] = PinType::Gpio(1); 139 | header_rev1[12] = PinType::Gpio(21); 140 | 141 | print_header(&header_rev1[..MAX_PINS_SHORT]) 142 | } 143 | Model::RaspberryPiA | Model::RaspberryPiBRev2 => print_header(&HEADER[..MAX_PINS_SHORT]), 144 | _ => print_header(&HEADER[..MAX_PINS_LONG]), 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/spi/hal.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Segment, Spi}; 2 | 3 | #[cfg(feature = "embedded-hal")] 4 | impl embedded_hal::spi::ErrorType for Spi { 5 | type Error = Error; 6 | } 7 | 8 | #[cfg(feature = "embedded-hal")] 9 | impl embedded_hal::spi::Error for Error { 10 | fn kind(&self) -> embedded_hal::spi::ErrorKind { 11 | embedded_hal::spi::ErrorKind::Other 12 | } 13 | } 14 | 15 | #[cfg(feature = "embedded-hal")] 16 | impl embedded_hal::spi::SpiBus for Spi { 17 | fn read(&mut self, words: &mut [u8]) -> Result<(), Self::Error> { 18 | Spi::read(self, words)?; 19 | Ok(()) 20 | } 21 | 22 | fn write(&mut self, words: &[u8]) -> Result<(), Self::Error> { 23 | Spi::write(self, words)?; 24 | Ok(()) 25 | } 26 | 27 | fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), Self::Error> { 28 | Spi::transfer(self, read, write)?; 29 | Ok(()) 30 | } 31 | 32 | fn transfer_in_place(&mut self, buffer: &mut [u8]) -> Result<(), Self::Error> { 33 | let write_buffer = buffer.to_vec(); 34 | self.transfer(buffer, &write_buffer) 35 | } 36 | 37 | fn flush(&mut self) -> Result<(), Self::Error> { 38 | Ok(()) 39 | } 40 | } 41 | 42 | #[cfg(feature = "embedded-hal-0")] 43 | impl embedded_hal_0::blocking::spi::Transfer for Spi { 44 | type Error = Error; 45 | 46 | fn transfer<'a>(&mut self, buffer: &'a mut [u8]) -> Result<&'a [u8], Self::Error> { 47 | let write_buffer = buffer.to_vec(); 48 | embedded_hal::spi::SpiBus::transfer(self, buffer, &write_buffer)?; 49 | Ok(buffer) 50 | } 51 | } 52 | 53 | #[cfg(feature = "embedded-hal-0")] 54 | impl embedded_hal_0::blocking::spi::Write for Spi { 55 | type Error = Error; 56 | 57 | fn write(&mut self, buffer: &[u8]) -> Result<(), Self::Error> { 58 | embedded_hal::spi::SpiBus::write(self, buffer) 59 | } 60 | } 61 | 62 | #[cfg(feature = "embedded-hal-nb")] 63 | impl embedded_hal_nb::spi::FullDuplex for Spi { 64 | fn read(&mut self) -> embedded_hal_nb::nb::Result { 65 | if let Some(last_read) = self.last_read.take() { 66 | Ok(last_read) 67 | } else { 68 | Err(embedded_hal_nb::nb::Error::WouldBlock) 69 | } 70 | } 71 | 72 | fn write(&mut self, byte: u8) -> embedded_hal_nb::nb::Result<(), Self::Error> { 73 | let mut read_buffer: [u8; 1] = [0]; 74 | 75 | Spi::transfer(self, &mut read_buffer, &[byte])?; 76 | self.last_read = Some(read_buffer[0]); 77 | 78 | Ok(()) 79 | } 80 | } 81 | 82 | #[cfg(feature = "embedded-hal-0")] 83 | impl embedded_hal_0::spi::FullDuplex for Spi { 84 | type Error = Error; 85 | 86 | fn read(&mut self) -> nb::Result { 87 | embedded_hal_nb::spi::FullDuplex::read(self) 88 | } 89 | 90 | fn send(&mut self, byte: u8) -> nb::Result<(), Self::Error> { 91 | embedded_hal_nb::spi::FullDuplex::write(self, byte) 92 | } 93 | } 94 | 95 | /// Simple implementation of [embedded_hal::spi::SpiDevice] 96 | /// 97 | /// You only need this when using the `embedded_hal` Spi trait interface. 98 | /// 99 | /// Slave-select is currently handled at the bus level. 100 | /// This no-op device implementation can be used to satisfy the trait. 101 | // TODO: The underlying crate::spi::Spi shall be split up to support proper slave-select handling here. 102 | pub struct SimpleHalSpiDevice { 103 | bus: Spi, 104 | } 105 | 106 | #[cfg(feature = "embedded-hal")] 107 | impl SimpleHalSpiDevice { 108 | pub fn new(bus: Spi) -> SimpleHalSpiDevice { 109 | SimpleHalSpiDevice { bus } 110 | } 111 | } 112 | 113 | #[cfg(feature = "embedded-hal")] 114 | impl embedded_hal::spi::SpiDevice for SimpleHalSpiDevice { 115 | fn transaction( 116 | &mut self, 117 | operations: &mut [embedded_hal::spi::Operation<'_, u8>], 118 | ) -> Result<(), Error> { 119 | let clock_speed = self.bus.clock_speed()?; 120 | let bits_per_word = self.bus.bits_per_word()?; 121 | 122 | // Map the hal spi operations to segments, so they all can be executed together as one transaction 123 | let segments = operations 124 | .into_iter() 125 | .map(|op| match op { 126 | embedded_hal::spi::Operation::Read(read_buff) => Segment::with_read(read_buff), 127 | embedded_hal::spi::Operation::Write(write_buff) => Segment::with_write(write_buff), 128 | embedded_hal::spi::Operation::Transfer(read_buff, write_buff) => { 129 | Segment::new(read_buff, write_buff) 130 | } 131 | embedded_hal::spi::Operation::TransferInPlace(buff) => { 132 | Segment::with_settings(Some(buff), None, clock_speed, 0, bits_per_word, false) 133 | } 134 | // Map a segment with no read or write buffer, just to handle the delay 135 | embedded_hal::spi::Operation::DelayNs(delay_ns) => Segment::with_settings( 136 | None, 137 | None, 138 | clock_speed, 139 | (*delay_ns / 1000) as u16, 140 | bits_per_word, 141 | false, 142 | ), 143 | }) 144 | .collect::>(); 145 | self.bus.transfer_segments(&segments) 146 | } 147 | } 148 | 149 | #[cfg(feature = "embedded-hal")] 150 | impl embedded_hal::spi::ErrorType for SimpleHalSpiDevice { 151 | type Error = Error; 152 | } 153 | -------------------------------------------------------------------------------- /src/gpio/hal.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | 3 | use super::{InputPin, IoPin, Level, OutputPin, Pin}; 4 | 5 | #[cfg(feature = "embedded-hal")] 6 | impl embedded_hal::digital::ErrorType for Pin { 7 | type Error = Infallible; 8 | } 9 | 10 | #[cfg(feature = "embedded-hal")] 11 | impl embedded_hal::digital::InputPin for Pin { 12 | fn is_high(&mut self) -> Result { 13 | Ok(Self::read(self) == Level::High) 14 | } 15 | 16 | fn is_low(&mut self) -> Result { 17 | Ok(Self::read(self) == Level::Low) 18 | } 19 | } 20 | 21 | #[cfg(feature = "embedded-hal")] 22 | impl embedded_hal::digital::ErrorType for InputPin { 23 | type Error = Infallible; 24 | } 25 | 26 | #[cfg(feature = "embedded-hal")] 27 | impl embedded_hal::digital::InputPin for InputPin { 28 | fn is_high(&mut self) -> Result { 29 | Ok((*self).is_high()) 30 | } 31 | 32 | fn is_low(&mut self) -> Result { 33 | Ok((*self).is_low()) 34 | } 35 | } 36 | 37 | #[cfg(feature = "embedded-hal")] 38 | impl embedded_hal::digital::ErrorType for IoPin { 39 | type Error = Infallible; 40 | } 41 | 42 | #[cfg(feature = "embedded-hal")] 43 | impl embedded_hal::digital::InputPin for IoPin { 44 | fn is_high(&mut self) -> Result { 45 | Ok((*self).is_high()) 46 | } 47 | 48 | fn is_low(&mut self) -> Result { 49 | Ok((*self).is_low()) 50 | } 51 | } 52 | 53 | #[cfg(feature = "embedded-hal")] 54 | impl embedded_hal::digital::ErrorType for OutputPin { 55 | type Error = Infallible; 56 | } 57 | 58 | #[cfg(feature = "embedded-hal")] 59 | impl embedded_hal::digital::InputPin for OutputPin { 60 | fn is_high(&mut self) -> Result { 61 | Ok(Self::is_set_high(self)) 62 | } 63 | 64 | fn is_low(&mut self) -> Result { 65 | Ok(Self::is_set_low(self)) 66 | } 67 | } 68 | 69 | #[cfg(feature = "embedded-hal")] 70 | impl embedded_hal::digital::OutputPin for OutputPin { 71 | fn set_low(&mut self) -> Result<(), Self::Error> { 72 | OutputPin::set_low(self); 73 | 74 | Ok(()) 75 | } 76 | 77 | fn set_high(&mut self) -> Result<(), Self::Error> { 78 | OutputPin::set_high(self); 79 | 80 | Ok(()) 81 | } 82 | } 83 | 84 | #[cfg(feature = "embedded-hal-0")] 85 | impl embedded_hal_0::digital::v2::OutputPin for OutputPin { 86 | type Error = Infallible; 87 | 88 | fn set_low(&mut self) -> Result<(), Self::Error> { 89 | embedded_hal::digital::OutputPin::set_low(self) 90 | } 91 | 92 | fn set_high(&mut self) -> Result<(), Self::Error> { 93 | embedded_hal::digital::OutputPin::set_high(self) 94 | } 95 | } 96 | 97 | #[cfg(feature = "embedded-hal")] 98 | impl embedded_hal::digital::StatefulOutputPin for OutputPin { 99 | fn is_set_high(&mut self) -> Result { 100 | Ok(OutputPin::is_set_high(self)) 101 | } 102 | 103 | fn is_set_low(&mut self) -> Result { 104 | Ok(OutputPin::is_set_low(self)) 105 | } 106 | 107 | fn toggle(&mut self) -> Result<(), Self::Error> { 108 | OutputPin::toggle(self); 109 | 110 | Ok(()) 111 | } 112 | } 113 | 114 | #[cfg(feature = "embedded-hal")] 115 | impl embedded_hal::digital::OutputPin for IoPin { 116 | fn set_low(&mut self) -> Result<(), Self::Error> { 117 | IoPin::set_low(self); 118 | 119 | Ok(()) 120 | } 121 | 122 | fn set_high(&mut self) -> Result<(), Self::Error> { 123 | IoPin::set_high(self); 124 | 125 | Ok(()) 126 | } 127 | } 128 | 129 | #[cfg(feature = "embedded-hal-0")] 130 | impl embedded_hal_0::digital::v2::OutputPin for IoPin { 131 | type Error = Infallible; 132 | 133 | fn set_low(&mut self) -> Result<(), Self::Error> { 134 | embedded_hal::digital::OutputPin::set_low(self) 135 | } 136 | 137 | fn set_high(&mut self) -> Result<(), Self::Error> { 138 | embedded_hal::digital::OutputPin::set_high(self) 139 | } 140 | } 141 | 142 | #[cfg(feature = "embedded-hal")] 143 | impl embedded_hal::digital::StatefulOutputPin for IoPin { 144 | fn is_set_high(&mut self) -> Result { 145 | Ok(IoPin::is_high(self)) 146 | } 147 | 148 | fn is_set_low(&mut self) -> Result { 149 | Ok(IoPin::is_low(self)) 150 | } 151 | 152 | fn toggle(&mut self) -> Result<(), Self::Error> { 153 | IoPin::toggle(self); 154 | 155 | Ok(()) 156 | } 157 | } 158 | 159 | #[cfg(feature = "embedded-hal-0")] 160 | impl embedded_hal_0::PwmPin for OutputPin { 161 | type Duty = f64; 162 | 163 | fn disable(&mut self) { 164 | let _ = self.clear_pwm(); 165 | } 166 | 167 | fn enable(&mut self) { 168 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 169 | } 170 | 171 | fn get_duty(&self) -> Self::Duty { 172 | self.duty_cycle 173 | } 174 | 175 | fn get_max_duty(&self) -> Self::Duty { 176 | 1.0 177 | } 178 | 179 | fn set_duty(&mut self, duty: Self::Duty) { 180 | self.duty_cycle = duty.clamp(0.0, 1.0); 181 | 182 | if self.soft_pwm.is_some() { 183 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 184 | } 185 | } 186 | } 187 | 188 | #[cfg(feature = "embedded-hal-0")] 189 | impl embedded_hal_0::PwmPin for IoPin { 190 | type Duty = f64; 191 | 192 | fn disable(&mut self) { 193 | let _ = self.clear_pwm(); 194 | } 195 | 196 | fn enable(&mut self) { 197 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 198 | } 199 | 200 | fn get_duty(&self) -> Self::Duty { 201 | self.duty_cycle 202 | } 203 | 204 | fn get_max_duty(&self) -> Self::Duty { 205 | 1.0 206 | } 207 | 208 | fn set_duty(&mut self, duty: Self::Duty) { 209 | self.duty_cycle = duty.clamp(0.0, 1.0); 210 | 211 | if self.soft_pwm.is_some() { 212 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 213 | } 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/gpio/soft_pwm.rs: -------------------------------------------------------------------------------- 1 | // Prevent warning when casting u32 as i64 2 | #![allow(clippy::cast_lossless)] 3 | #![allow(dead_code)] 4 | 5 | use std::sync::mpsc::{self, Receiver, Sender}; 6 | use std::sync::Arc; 7 | use std::thread::{self, sleep}; 8 | use std::time::Duration; 9 | 10 | use libc::{self, sched_param, timespec, CLOCK_MONOTONIC, PR_SET_TIMERSLACK, SCHED_RR}; 11 | 12 | use super::{Error, GpioState, Result}; 13 | 14 | // Only call sleep() if we have enough time remaining 15 | const SLEEP_THRESHOLD: i64 = 250_000; 16 | // Reserve some time for busy waiting 17 | const BUSYWAIT_MAX: i64 = 200_000; 18 | // Subtract from the remaining busy wait time to account for get_time_ns() overhead 19 | const BUSYWAIT_REMAINDER: i64 = 100; 20 | 21 | const NANOS_PER_SEC: i64 = 1_000_000_000; 22 | 23 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 24 | enum Msg { 25 | Reconfigure(Duration, Duration), 26 | Stop, 27 | } 28 | 29 | #[derive(Debug)] 30 | pub(crate) struct SoftPwm { 31 | pwm_thread: Option>>, 32 | sender: Sender, 33 | } 34 | 35 | impl SoftPwm { 36 | pub(crate) fn new( 37 | pin: u8, 38 | gpio_state: Arc, 39 | period: Duration, 40 | pulse_width: Duration, 41 | ) -> SoftPwm { 42 | let (sender, receiver): (Sender, Receiver) = mpsc::channel(); 43 | 44 | let pwm_thread = thread::spawn(move || -> Result<()> { 45 | // Set the scheduling policy to real-time round robin at the highest priority. This 46 | // will silently fail if we're not running as root. 47 | #[cfg(target_env = "gnu")] 48 | let params = sched_param { 49 | sched_priority: unsafe { libc::sched_get_priority_max(SCHED_RR) }, 50 | }; 51 | 52 | #[cfg(target_env = "musl")] 53 | let params = sched_param { 54 | sched_priority: unsafe { libc::sched_get_priority_max(SCHED_RR) }, 55 | sched_ss_low_priority: 0, 56 | sched_ss_repl_period: timespec { 57 | tv_sec: 0, 58 | tv_nsec: 0, 59 | }, 60 | sched_ss_init_budget: timespec { 61 | tv_sec: 0, 62 | tv_nsec: 0, 63 | }, 64 | sched_ss_max_repl: 0, 65 | }; 66 | 67 | unsafe { 68 | libc::sched_setscheduler(0, SCHED_RR, ¶ms); 69 | } 70 | 71 | // Set timer slack to 1 ns (default = 50 µs). This is only relevant if we're unable 72 | // to set a real-time scheduling policy. 73 | unsafe { 74 | libc::prctl(PR_SET_TIMERSLACK, 1); 75 | } 76 | 77 | let mut period_ns = period.as_nanos() as i64; 78 | let mut pulse_width_ns = pulse_width.as_nanos() as i64; 79 | 80 | let mut start_ns = get_time_ns(); 81 | 82 | loop { 83 | // PWM active 84 | if pulse_width_ns > 0 { 85 | gpio_state.gpio_mem.set_high(pin); 86 | } 87 | 88 | // Sleep if we have enough time remaining, while reserving some time 89 | // for busy waiting to compensate for sleep taking longer than needed. 90 | if pulse_width_ns >= SLEEP_THRESHOLD { 91 | sleep(Duration::from_nanos((pulse_width_ns - BUSYWAIT_MAX) as u64)); 92 | } 93 | 94 | // Busy-wait for the remaining active time, minus BUSYWAIT_REMAINDER 95 | // to account for get_time_ns() overhead 96 | loop { 97 | if (pulse_width_ns - (get_time_ns() - start_ns)) <= BUSYWAIT_REMAINDER { 98 | break; 99 | } 100 | } 101 | 102 | // PWM inactive 103 | gpio_state.gpio_mem.set_low(pin); 104 | 105 | while let Ok(msg) = receiver.try_recv() { 106 | match msg { 107 | Msg::Reconfigure(period, pulse_width) => { 108 | // Reconfigure period and pulse width 109 | pulse_width_ns = pulse_width.as_nanos() as i64; 110 | period_ns = period.as_nanos() as i64; 111 | 112 | if pulse_width_ns > period_ns { 113 | pulse_width_ns = period_ns; 114 | } 115 | } 116 | Msg::Stop => { 117 | // The main thread asked us to stop 118 | return Ok(()); 119 | } 120 | } 121 | } 122 | 123 | let remaining_ns = period_ns - (get_time_ns() - start_ns); 124 | 125 | // Sleep if we have enough time remaining, while reserving some time 126 | // for busy waiting to compensate for sleep taking longer than needed. 127 | if remaining_ns >= SLEEP_THRESHOLD { 128 | sleep(Duration::from_nanos((remaining_ns - BUSYWAIT_MAX) as u64)); 129 | } 130 | 131 | // Busy-wait for the remaining inactive time, minus BUSYWAIT_REMAINDER 132 | // to account for get_time_ns() overhead 133 | loop { 134 | let current_ns = get_time_ns(); 135 | if (period_ns - (current_ns - start_ns)) <= BUSYWAIT_REMAINDER { 136 | start_ns = current_ns; 137 | break; 138 | } 139 | } 140 | } 141 | }); 142 | 143 | SoftPwm { 144 | pwm_thread: Some(pwm_thread), 145 | sender, 146 | } 147 | } 148 | 149 | pub(crate) fn reconfigure(&mut self, period: Duration, pulse_width: Duration) { 150 | let _ = self.sender.send(Msg::Reconfigure(period, pulse_width)); 151 | } 152 | 153 | pub(crate) fn stop(&mut self) -> Result<()> { 154 | let _ = self.sender.send(Msg::Stop); 155 | if let Some(pwm_thread) = self.pwm_thread.take() { 156 | match pwm_thread.join() { 157 | Ok(r) => return r, 158 | Err(_) => return Err(Error::ThreadPanic), 159 | } 160 | } 161 | 162 | Ok(()) 163 | } 164 | } 165 | 166 | impl Drop for SoftPwm { 167 | fn drop(&mut self) { 168 | // Don't wait for the pwm thread to exit if the main thread is panicking, 169 | // because we could potentially block indefinitely while unwinding if the 170 | // pwm thread doesn't respond to the Stop message for some reason. 171 | if !thread::panicking() { 172 | let _ = self.stop(); 173 | } 174 | } 175 | } 176 | 177 | // Required because Sender isn't Sync. Implementing Sync for SoftPwm is 178 | // safe because all usage of Sender::send() is locked behind &mut self. 179 | unsafe impl Sync for SoftPwm {} 180 | 181 | #[inline(always)] 182 | fn get_time_ns() -> i64 { 183 | let mut ts = timespec { 184 | tv_sec: 0, 185 | tv_nsec: 0, 186 | }; 187 | 188 | unsafe { 189 | libc::clock_gettime(CLOCK_MONOTONIC, &mut ts); 190 | } 191 | 192 | (ts.tv_sec as i64 * NANOS_PER_SEC) + ts.tv_nsec as i64 193 | } 194 | -------------------------------------------------------------------------------- /src/gpio/hal_unproven.rs: -------------------------------------------------------------------------------- 1 | use core::convert::Infallible; 2 | use std::time::Duration; 3 | 4 | use super::{InputPin, IoPin, Level, Mode, OutputPin, Pin}; 5 | 6 | const NANOS_PER_SEC: f64 = 1_000_000_000.0; 7 | 8 | #[cfg(feature = "embedded-hal-0")] 9 | impl embedded_hal_0::digital::v2::InputPin for Pin { 10 | type Error = Infallible; 11 | 12 | fn is_high(&self) -> Result { 13 | Ok((*self).read() == Level::High) 14 | } 15 | 16 | fn is_low(&self) -> Result { 17 | Ok((*self).read() == Level::Low) 18 | } 19 | } 20 | 21 | #[cfg(feature = "embedded-hal-0")] 22 | impl embedded_hal_0::digital::v2::InputPin for InputPin { 23 | type Error = Infallible; 24 | 25 | fn is_high(&self) -> Result { 26 | Ok((*self).is_high()) 27 | } 28 | 29 | fn is_low(&self) -> Result { 30 | Ok((*self).is_high()) 31 | } 32 | } 33 | 34 | #[cfg(feature = "embedded-hal-0")] 35 | impl embedded_hal_0::digital::v2::InputPin for IoPin { 36 | type Error = Infallible; 37 | 38 | fn is_high(&self) -> Result { 39 | Ok((*self).is_high()) 40 | } 41 | 42 | fn is_low(&self) -> Result { 43 | Ok((*self).is_high()) 44 | } 45 | } 46 | 47 | #[cfg(feature = "embedded-hal-0")] 48 | impl embedded_hal_0::digital::v2::InputPin for OutputPin { 49 | type Error = Infallible; 50 | 51 | fn is_high(&self) -> Result { 52 | Ok((*self).is_set_high()) 53 | } 54 | 55 | fn is_low(&self) -> Result { 56 | Ok((*self).is_set_low()) 57 | } 58 | } 59 | 60 | #[cfg(feature = "embedded-hal-0")] 61 | impl embedded_hal_0::digital::v2::StatefulOutputPin for IoPin { 62 | fn is_set_high(&self) -> Result { 63 | Ok((*self).is_high()) 64 | } 65 | 66 | fn is_set_low(&self) -> Result { 67 | Ok((*self).is_low()) 68 | } 69 | } 70 | 71 | #[cfg(feature = "embedded-hal-0")] 72 | impl embedded_hal_0::digital::v2::StatefulOutputPin for OutputPin { 73 | fn is_set_high(&self) -> Result { 74 | Ok((*self).is_set_high()) 75 | } 76 | 77 | fn is_set_low(&self) -> Result { 78 | Ok((*self).is_set_low()) 79 | } 80 | } 81 | 82 | #[cfg(feature = "embedded-hal-0")] 83 | impl embedded_hal_0::digital::v2::ToggleableOutputPin for IoPin { 84 | type Error = Infallible; 85 | 86 | fn toggle(&mut self) -> Result<(), Self::Error> { 87 | embedded_hal::digital::StatefulOutputPin::toggle(self) 88 | } 89 | } 90 | 91 | #[cfg(feature = "embedded-hal-0")] 92 | impl embedded_hal_0::digital::v2::ToggleableOutputPin for OutputPin { 93 | type Error = Infallible; 94 | 95 | fn toggle(&mut self) -> Result<(), Self::Error> { 96 | embedded_hal::digital::StatefulOutputPin::toggle(self) 97 | } 98 | } 99 | 100 | #[cfg(feature = "embedded-hal-0")] 101 | impl embedded_hal_0::Pwm for OutputPin { 102 | type Duty = f64; 103 | type Channel = (); 104 | type Time = Duration; 105 | 106 | /// Disables a PWM `channel`. 107 | fn disable(&mut self, _channel: Self::Channel) { 108 | let _ = self.clear_pwm(); 109 | } 110 | 111 | /// Enables a PWM `channel`. 112 | fn enable(&mut self, _channel: Self::Channel) { 113 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 114 | } 115 | 116 | /// Returns the current PWM period. 117 | fn get_period(&self) -> Self::Time { 118 | Duration::from_nanos(if self.frequency == 0.0 { 119 | 0 120 | } else { 121 | ((1.0 / self.frequency) * NANOS_PER_SEC) as u64 122 | }) 123 | } 124 | 125 | /// Returns the current duty cycle. 126 | fn get_duty(&self, _channel: Self::Channel) -> Self::Duty { 127 | self.duty_cycle 128 | } 129 | 130 | /// Returns the maximum duty cycle value. 131 | fn get_max_duty(&self) -> Self::Duty { 132 | 1.0 133 | } 134 | 135 | /// Sets a new duty cycle. 136 | fn set_duty(&mut self, _channel: Self::Channel, duty: Self::Duty) { 137 | self.duty_cycle = duty.clamp(0.0, 1.0); 138 | 139 | if self.soft_pwm.is_some() { 140 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 141 | } 142 | } 143 | 144 | /// Sets a new PWM period. 145 | fn set_period

(&mut self, period: P) 146 | where 147 | P: Into, 148 | { 149 | let period = period.into(); 150 | self.frequency = 151 | 1.0 / (period.as_secs() as f64 + (f64::from(period.subsec_nanos()) / NANOS_PER_SEC)); 152 | 153 | if self.soft_pwm.is_some() { 154 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 155 | } 156 | } 157 | } 158 | 159 | #[cfg(feature = "embedded-hal-0")] 160 | impl embedded_hal_0::Pwm for IoPin { 161 | type Duty = f64; 162 | type Channel = (); 163 | type Time = Duration; 164 | 165 | /// Disables a PWM `channel`. 166 | fn disable(&mut self, _channel: Self::Channel) { 167 | let _ = self.clear_pwm(); 168 | } 169 | 170 | /// Enables a PWM `channel`. 171 | fn enable(&mut self, _channel: Self::Channel) { 172 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 173 | } 174 | 175 | /// Returns the current PWM period. 176 | fn get_period(&self) -> Self::Time { 177 | Duration::from_nanos(if self.frequency == 0.0 { 178 | 0 179 | } else { 180 | ((1.0 / self.frequency) * NANOS_PER_SEC) as u64 181 | }) 182 | } 183 | 184 | /// Returns the current duty cycle. 185 | fn get_duty(&self, _channel: Self::Channel) -> Self::Duty { 186 | self.duty_cycle 187 | } 188 | 189 | /// Returns the maximum duty cycle value. 190 | fn get_max_duty(&self) -> Self::Duty { 191 | 1.0 192 | } 193 | 194 | /// Sets a new duty cycle. 195 | fn set_duty(&mut self, _channel: Self::Channel, duty: Self::Duty) { 196 | self.duty_cycle = duty.clamp(0.0, 1.0); 197 | 198 | if self.soft_pwm.is_some() { 199 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 200 | } 201 | } 202 | 203 | /// Sets a new PWM period. 204 | fn set_period

(&mut self, period: P) 205 | where 206 | P: Into, 207 | { 208 | let period = period.into(); 209 | self.frequency = 210 | 1.0 / (period.as_secs() as f64 + (f64::from(period.subsec_nanos()) / NANOS_PER_SEC)); 211 | 212 | if self.soft_pwm.is_some() { 213 | let _ = self.set_pwm_frequency(self.frequency, self.duty_cycle); 214 | } 215 | } 216 | } 217 | 218 | #[cfg(feature = "embedded-hal-0")] 219 | impl embedded_hal_0::digital::v2::IoPin for IoPin { 220 | type Error = Infallible; 221 | 222 | /// Tries to convert this pin to input mode. 223 | /// 224 | /// If the pin is already in input mode, this method should succeed. 225 | fn into_input_pin(mut self) -> Result { 226 | if self.mode() != Mode::Input { 227 | self.set_mode(Mode::Input); 228 | } 229 | 230 | Ok(self) 231 | } 232 | 233 | /// Tries to convert this pin to output mode with the given initial state. 234 | /// 235 | /// If the pin is already in the requested state, this method should 236 | /// succeed. 237 | fn into_output_pin( 238 | mut self, 239 | state: embedded_hal_0::digital::v2::PinState, 240 | ) -> Result { 241 | match state { 242 | embedded_hal_0::digital::v2::PinState::Low => self.set_low(), 243 | embedded_hal_0::digital::v2::PinState::High => self.set_high(), 244 | } 245 | 246 | if self.mode() != Mode::Output { 247 | self.set_mode(Mode::Output); 248 | } 249 | 250 | Ok(self) 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/pwm/sysfs.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unnecessary_cast)] 2 | 3 | use std::ffi::CString; 4 | use std::fs; 5 | use std::fs::File; 6 | use std::io; 7 | use std::io::Write; 8 | use std::os::unix::fs::{MetadataExt, PermissionsExt}; 9 | use std::path::Path; 10 | use std::ptr; 11 | use std::result; 12 | use std::thread; 13 | use std::time::Duration; 14 | 15 | use libc::{c_char, group, passwd}; 16 | 17 | use crate::pwm::Polarity; 18 | 19 | /// Result type returned from methods that can have `io::Error`s. 20 | pub type Result = result::Result; 21 | 22 | // Find user ID for specified user 23 | pub fn user_to_uid(name: &str) -> Option { 24 | if let Ok(name_cstr) = CString::new(name) { 25 | let buf = &mut [0 as c_char; 4096]; 26 | let mut res: *mut passwd = ptr::null_mut(); 27 | let mut pwd = passwd { 28 | pw_name: ptr::null_mut(), 29 | pw_passwd: ptr::null_mut(), 30 | pw_uid: 0, 31 | pw_gid: 0, 32 | pw_gecos: ptr::null_mut(), 33 | pw_dir: ptr::null_mut(), 34 | pw_shell: ptr::null_mut(), 35 | }; 36 | 37 | unsafe { 38 | if libc::getpwnam_r( 39 | name_cstr.as_ptr(), 40 | &mut pwd, 41 | buf.as_mut_ptr(), 42 | buf.len(), 43 | &mut res, 44 | ) == 0 45 | && res as usize > 0 46 | { 47 | return Some((*res).pw_uid); 48 | } 49 | } 50 | } 51 | 52 | None 53 | } 54 | 55 | // Find group ID for specified group 56 | pub fn group_to_gid(name: &str) -> Option { 57 | if let Ok(name_cstr) = CString::new(name) { 58 | let buf = &mut [0 as c_char; 4096]; 59 | let mut res: *mut group = ptr::null_mut(); 60 | let mut grp = group { 61 | gr_name: ptr::null_mut(), 62 | gr_passwd: ptr::null_mut(), 63 | gr_gid: 0, 64 | gr_mem: ptr::null_mut(), 65 | }; 66 | 67 | unsafe { 68 | if libc::getgrnam_r( 69 | name_cstr.as_ptr(), 70 | &mut grp, 71 | buf.as_mut_ptr(), 72 | buf.len(), 73 | &mut res, 74 | ) == 0 75 | && res as usize > 0 76 | { 77 | return Some((*res).gr_gid); 78 | } 79 | } 80 | } 81 | 82 | None 83 | } 84 | 85 | // Check file permissions and group ID 86 | fn check_permissions(path: &str, gid: u32) -> bool { 87 | if let Ok(metadata) = fs::metadata(path) { 88 | if metadata.permissions().mode() != 0o040_770 && metadata.permissions().mode() != 0o100_770 89 | { 90 | return false; 91 | } 92 | 93 | if metadata.gid() == gid { 94 | return true; 95 | } 96 | } 97 | 98 | false 99 | } 100 | 101 | pub fn export(chip: u8, channel: u8) -> Result<()> { 102 | // Only export if the channel isn't already exported 103 | if !Path::new(&format!("/sys/class/pwm/pwmchip{}/pwm{}", chip, channel)).exists() { 104 | File::create(format!("/sys/class/pwm/pwmchip{}/export", chip))? 105 | .write_fmt(format_args!("{}", channel))?; 106 | } 107 | 108 | // If we're logged in as root or effective root, skip the permission checks 109 | if let Some(root_uid) = user_to_uid("root") { 110 | unsafe { 111 | if libc::getuid() == root_uid || libc::geteuid() == root_uid { 112 | return Ok(()); 113 | } 114 | } 115 | } 116 | 117 | // Wait 1s max for the group to change to gpio, and group permissions to be set, 118 | // provided the proper udev rules have been set up and a recent kernel is installed, which 119 | // avoids running into permission issues where root access is required. This might require 120 | // manually adding rules, since they don't seem to be part of the latest release yet. The 121 | // patched drivers/pwm/sysfs.c was included in raspberrypi-kernel_1.20180417-1 (4.14.34). 122 | // See: https://github.com/raspberrypi/linux/issues/1983 123 | let gid_gpio = group_to_gid("gpio").unwrap_or_default(); 124 | 125 | let paths = &[ 126 | format!("/sys/class/pwm/pwmchip{}/pwm{}", chip, channel), 127 | format!("/sys/class/pwm/pwmchip{}/pwm{}/period", chip, channel), 128 | format!("/sys/class/pwm/pwmchip{}/pwm{}/duty_cycle", chip, channel), 129 | format!("/sys/class/pwm/pwmchip{}/pwm{}/polarity", chip, channel), 130 | format!("/sys/class/pwm/pwmchip{}/pwm{}/enable", chip, channel), 131 | ]; 132 | 133 | let mut counter = 0; 134 | 'counter: while counter < 25 { 135 | for path in paths { 136 | if !check_permissions(path, gid_gpio) { 137 | // This should normally be set within the first ~30ms. 138 | thread::sleep(Duration::from_millis(40)); 139 | counter += 1; 140 | 141 | continue 'counter; 142 | } 143 | } 144 | 145 | break; 146 | } 147 | 148 | Ok(()) 149 | } 150 | 151 | pub fn unexport(chip: u8, channel: u8) -> Result<()> { 152 | // Only unexport if the channel is actually exported 153 | if Path::new(&format!("/sys/class/pwm/pwmchip{}/pwm{}", chip, channel)).exists() { 154 | File::create(format!("/sys/class/pwm/pwmchip{}/unexport", chip))? 155 | .write_fmt(format_args!("{}", channel))?; 156 | } 157 | 158 | Ok(()) 159 | } 160 | 161 | pub fn period(chip: u8, channel: u8) -> Result { 162 | let period = fs::read_to_string(format!( 163 | "/sys/class/pwm/pwmchip{}/pwm{}/period", 164 | chip, channel 165 | ))?; 166 | if let Ok(period) = period.trim().parse() { 167 | Ok(period) 168 | } else { 169 | Ok(0) 170 | } 171 | } 172 | 173 | pub fn set_period(chip: u8, channel: u8, period: u64) -> Result<()> { 174 | File::create(format!( 175 | "/sys/class/pwm/pwmchip{}/pwm{}/period", 176 | chip, channel 177 | ))? 178 | .write_fmt(format_args!("{}", period))?; 179 | 180 | Ok(()) 181 | } 182 | 183 | pub fn pulse_width(chip: u8, channel: u8) -> Result { 184 | // The sysfs PWM interface specifies the duty cycle in nanoseconds, which 185 | // means it's actually the pulse width. 186 | let duty_cycle = fs::read_to_string(format!( 187 | "/sys/class/pwm/pwmchip{}/pwm{}/duty_cycle", 188 | chip, channel 189 | ))?; 190 | 191 | if let Ok(duty_cycle) = duty_cycle.trim().parse() { 192 | Ok(duty_cycle) 193 | } else { 194 | Ok(0) 195 | } 196 | } 197 | 198 | pub fn set_pulse_width(chip: u8, channel: u8, pulse_width: u64) -> Result<()> { 199 | // The sysfs PWM interface specifies the duty cycle in nanoseconds, which 200 | // means it's actually the pulse width. 201 | File::create(format!( 202 | "/sys/class/pwm/pwmchip{}/pwm{}/duty_cycle", 203 | chip, channel 204 | ))? 205 | .write_fmt(format_args!("{}", pulse_width))?; 206 | 207 | Ok(()) 208 | } 209 | 210 | pub fn polarity(chip: u8, channel: u8) -> Result { 211 | let polarity = fs::read_to_string(format!( 212 | "/sys/class/pwm/pwmchip{}/pwm{}/polarity", 213 | chip, channel 214 | ))?; 215 | 216 | match polarity.trim() { 217 | "normal" => Ok(Polarity::Normal), 218 | _ => Ok(Polarity::Inverse), 219 | } 220 | } 221 | 222 | pub fn set_polarity(chip: u8, channel: u8, polarity: Polarity) -> Result<()> { 223 | let b_polarity: &[u8] = match polarity { 224 | Polarity::Normal => b"normal", 225 | Polarity::Inverse => b"inversed", 226 | }; 227 | 228 | File::create(format!( 229 | "/sys/class/pwm/pwmchip{}/pwm{}/polarity", 230 | chip, channel 231 | ))? 232 | .write_all(b_polarity)?; 233 | 234 | Ok(()) 235 | } 236 | 237 | pub fn enabled(chip: u8, channel: u8) -> Result { 238 | let enabled = fs::read_to_string(format!( 239 | "/sys/class/pwm/pwmchip{}/pwm{}/enable", 240 | chip, channel 241 | ))?; 242 | 243 | match enabled.trim() { 244 | "0" => Ok(false), 245 | _ => Ok(true), 246 | } 247 | } 248 | 249 | pub fn set_enabled(chip: u8, channel: u8, enabled: bool) -> Result<()> { 250 | File::create(format!( 251 | "/sys/class/pwm/pwmchip{}/pwm{}/enable", 252 | chip, channel 253 | ))? 254 | .write_fmt(format_args!("{}", enabled as u8)) 255 | .map_err(|e| { 256 | if e.kind() == io::ErrorKind::InvalidInput { 257 | io::Error::new( 258 | io::ErrorKind::InvalidInput, 259 | "Make sure you have set either a period or frequency before enabling PWM", 260 | ) 261 | } else { 262 | e 263 | } 264 | })?; 265 | 266 | Ok(()) 267 | } 268 | -------------------------------------------------------------------------------- /src/gpio/interrupt.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::fmt; 4 | use std::thread; 5 | use std::time::{Duration, Instant}; 6 | 7 | use crate::gpio::epoll::{epoll_event, Epoll, EventFd, EPOLLERR, EPOLLET, EPOLLIN, EPOLLPRI}; 8 | use crate::gpio::ioctl; 9 | use crate::gpio::pin::InputPin; 10 | use crate::gpio::{Error, Event, Result, Trigger}; 11 | 12 | pub(crate) struct EventLoop { 13 | poll: Epoll, 14 | events: Vec, 15 | trigger_status: Vec, 16 | cdev_fd: i32, 17 | } 18 | 19 | #[derive(Debug)] 20 | struct Interrupt { 21 | pin: u8, 22 | trigger: Trigger, 23 | debounce: Option, 24 | cdev_fd: i32, 25 | event_request: ioctl::EventRequest, 26 | } 27 | 28 | impl Interrupt { 29 | fn new( 30 | cdev_fd: i32, 31 | pin: u8, 32 | trigger: Trigger, 33 | debounce: Option, 34 | ) -> Result { 35 | Ok(Interrupt { 36 | pin, 37 | trigger, 38 | debounce, 39 | cdev_fd, 40 | event_request: ioctl::EventRequest::new(cdev_fd, pin, trigger, debounce)?, 41 | }) 42 | } 43 | 44 | fn trigger(&self) -> Trigger { 45 | self.trigger 46 | } 47 | 48 | fn fd(&self) -> i32 { 49 | self.event_request.fd() 50 | } 51 | 52 | fn pin(&self) -> u8 { 53 | self.pin 54 | } 55 | 56 | fn set_trigger(&mut self, trigger: Trigger) -> Result<()> { 57 | self.trigger = trigger; 58 | 59 | self.reset() 60 | } 61 | 62 | fn event(&mut self) -> Result { 63 | // This might block if there are no events waiting 64 | Ok(ioctl::LineEvent::new(self.event_request.fd())?.into_event()) 65 | } 66 | 67 | fn reset(&mut self) -> Result<()> { 68 | // Close the old event fd before opening a new one 69 | self.event_request.close(); 70 | self.event_request = 71 | ioctl::EventRequest::new(self.cdev_fd, self.pin, self.trigger, self.debounce)?; 72 | 73 | Ok(()) 74 | } 75 | } 76 | 77 | #[derive(Debug)] 78 | struct TriggerStatus { 79 | interrupt: Option, 80 | triggered: bool, 81 | event: Event, 82 | } 83 | 84 | impl fmt::Debug for EventLoop { 85 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 86 | f.debug_struct("EventLoop") 87 | .field("poll", &self.poll) 88 | .field("events", &format_args!("{{ .. }}")) 89 | .field("trigger_status", &format_args!("{{ .. }}")) 90 | .field("cdev_fd", &self.cdev_fd) 91 | .finish() 92 | } 93 | } 94 | 95 | impl EventLoop { 96 | pub fn new(cdev_fd: i32, capacity: usize) -> Result { 97 | let mut trigger_status = Vec::with_capacity(capacity); 98 | 99 | // Initialize trigger_status while circumventing the Copy/Clone requirement 100 | for _ in 0..trigger_status.capacity() { 101 | trigger_status.push(TriggerStatus { 102 | interrupt: None, 103 | triggered: false, 104 | event: Event::default(), 105 | }); 106 | } 107 | 108 | Ok(EventLoop { 109 | poll: Epoll::new()?, 110 | events: vec![epoll_event { events: 0, u64: 0 }; capacity], 111 | trigger_status, 112 | cdev_fd, 113 | }) 114 | } 115 | 116 | pub fn poll<'a>( 117 | &mut self, 118 | pins: &[&'a InputPin], 119 | reset: bool, 120 | timeout: Option, 121 | ) -> Result> { 122 | for pin in pins { 123 | let trigger_status = &mut self.trigger_status[pin.pin() as usize]; 124 | 125 | // Did we cache any trigger events during the previous poll? 126 | if trigger_status.triggered { 127 | trigger_status.triggered = false; 128 | 129 | if !reset { 130 | return Ok(Some((pin, trigger_status.event))); 131 | } 132 | } 133 | 134 | // Reset any pending trigger events 135 | if let Some(ref mut interrupt) = trigger_status.interrupt { 136 | if reset { 137 | self.poll.delete(interrupt.fd())?; 138 | interrupt.reset()?; 139 | self.poll.add( 140 | interrupt.fd(), 141 | u64::from(interrupt.pin()), 142 | EPOLLIN | EPOLLPRI, 143 | )?; 144 | } 145 | } 146 | } 147 | 148 | // Loop until we get any of the events we're waiting for, or a timeout occurs 149 | let now = Instant::now(); 150 | loop { 151 | let num_events = self.poll.wait(&mut self.events, timeout)?; 152 | 153 | // No events means a timeout occurred 154 | if num_events == 0 { 155 | return Ok(None); 156 | } 157 | 158 | for event in &self.events[0..num_events] { 159 | let pin = event.u64 as usize; 160 | 161 | let trigger_status = &mut self.trigger_status[pin]; 162 | 163 | if let Some(ref mut interrupt) = trigger_status.interrupt { 164 | trigger_status.event = interrupt.event()?; 165 | trigger_status.triggered = true; 166 | }; 167 | } 168 | 169 | // Were any interrupts triggered? If so, return one. The rest 170 | // will be saved for the next poll. 171 | for pin in pins { 172 | let trigger_status = &mut self.trigger_status[pin.pin() as usize]; 173 | 174 | if trigger_status.triggered { 175 | trigger_status.triggered = false; 176 | return Ok(Some((pin, trigger_status.event))); 177 | } 178 | } 179 | 180 | // It's possible a pin we're not waiting for continuously triggers 181 | // an interrupt, causing repeated loops with calls to poll() using a 182 | // reset timeout value. Make sure we haven't been looping longer than 183 | // the requested timeout. 184 | if let Some(t) = timeout { 185 | if now.elapsed() > t { 186 | return Ok(None); 187 | } 188 | } 189 | } 190 | } 191 | 192 | pub fn set_interrupt( 193 | &mut self, 194 | pin: u8, 195 | trigger: Trigger, 196 | debounce: Option, 197 | ) -> Result<()> { 198 | let trigger_status = &mut self.trigger_status[pin as usize]; 199 | 200 | trigger_status.triggered = false; 201 | 202 | // Interrupt already exists. We just need to change the trigger. 203 | if let Some(ref mut interrupt) = trigger_status.interrupt { 204 | if interrupt.trigger != trigger { 205 | // This requires a new event request, so the fd might change 206 | self.poll.delete(interrupt.fd())?; 207 | interrupt.set_trigger(trigger)?; 208 | self.poll 209 | .add(interrupt.fd(), u64::from(pin), EPOLLIN | EPOLLPRI)?; 210 | } 211 | 212 | return Ok(()); 213 | } 214 | 215 | // Register a new interrupt 216 | let interrupt = Interrupt::new(self.cdev_fd, pin, trigger, debounce)?; 217 | self.poll 218 | .add(interrupt.fd(), u64::from(pin), EPOLLIN | EPOLLPRI)?; 219 | trigger_status.interrupt = Some(interrupt); 220 | 221 | Ok(()) 222 | } 223 | 224 | pub fn clear_interrupt(&mut self, pin: u8) -> Result<()> { 225 | let trigger_status = &mut self.trigger_status[pin as usize]; 226 | 227 | trigger_status.triggered = false; 228 | 229 | if let Some(interrupt) = trigger_status.interrupt.take() { 230 | self.poll.delete(interrupt.fd())?; 231 | } 232 | 233 | Ok(()) 234 | } 235 | } 236 | 237 | #[derive(Debug)] 238 | pub struct AsyncInterrupt { 239 | poll_thread: Option>>, 240 | tx: EventFd, 241 | } 242 | 243 | impl AsyncInterrupt { 244 | pub fn new( 245 | fd: i32, 246 | pin: u8, 247 | trigger: Trigger, 248 | debounce: Option, 249 | mut callback: C, 250 | ) -> Result 251 | where 252 | C: FnMut(Event) + Send + 'static, 253 | { 254 | let tx = EventFd::new()?; 255 | let rx = tx.fd(); 256 | 257 | let poll_thread = thread::spawn(move || -> Result<()> { 258 | let poll = Epoll::new()?; 259 | 260 | // rx becomes readable when the main thread calls notify() 261 | poll.add(rx, rx as u64, EPOLLERR | EPOLLET | EPOLLIN)?; 262 | 263 | let mut interrupt = Interrupt::new(fd, pin, trigger, debounce)?; 264 | poll.add(interrupt.fd(), interrupt.fd() as u64, EPOLLIN | EPOLLPRI)?; 265 | 266 | let mut events = [epoll_event { events: 0, u64: 0 }; 2]; 267 | loop { 268 | let num_events = poll.wait(&mut events, None)?; 269 | if num_events > 0 { 270 | for event in &events[0..num_events] { 271 | let fd = event.u64 as i32; 272 | if fd == rx { 273 | return Ok(()); // The main thread asked us to stop 274 | } else if fd == interrupt.fd() { 275 | callback(interrupt.event()?); 276 | } 277 | } 278 | } 279 | } 280 | }); 281 | 282 | Ok(AsyncInterrupt { 283 | poll_thread: Some(poll_thread), 284 | tx, 285 | }) 286 | } 287 | 288 | pub fn stop(&mut self) -> Result<()> { 289 | self.tx.notify()?; 290 | 291 | if let Some(poll_thread) = self.poll_thread.take() { 292 | match poll_thread.join() { 293 | Ok(r) => return r, 294 | Err(_) => return Err(Error::ThreadPanic), 295 | } 296 | } 297 | 298 | Ok(()) 299 | } 300 | } 301 | 302 | impl Drop for AsyncInterrupt { 303 | fn drop(&mut self) { 304 | // Don't wait for the poll thread to exit if the main thread is panicking, 305 | // because we could potentially block indefinitely while unwinding if the 306 | // poll thread is executing a callback that doesn't return. 307 | if !thread::panicking() { 308 | let _ = self.stop(); 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/spi/segment.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::marker; 3 | 4 | /// Part of a multi-segment transfer. 5 | /// 6 | /// `Segment`s are transferred using the [`Spi::transfer_segments`] method. 7 | /// 8 | /// Construct a new `Segment` for a simultaneous (full-duplex) read/write 9 | /// transfer using [`new`]. For read operations without any outgoing data, 10 | /// use [`with_read`]. For write operations where any incoming data 11 | /// should be discarded, use [`with_write`]. 12 | /// 13 | /// [`Spi::transfer_segments`]: struct.Spi.html#method.transfer_segments 14 | /// [`with_read`]: #method.with_read 15 | /// [`with_write`]: #method.with_write 16 | /// [`new`]: #method.new 17 | #[derive(PartialEq, Eq, Copy, Clone)] 18 | #[repr(C)] 19 | pub struct Segment<'a, 'b> { 20 | // Pointer to write buffer, or 0. 21 | tx_buf: u64, 22 | // Pointer to read buffer, or 0. 23 | rx_buf: u64, 24 | // Number of bytes to transfer in this segment. 25 | len: u32, 26 | // Set a different clock speed for this segment. Default = 0. 27 | speed_hz: u32, 28 | // Add a delay before the (optional) SS change and the next segment. 29 | delay_usecs: u16, 30 | // Bits per word for this segment. The Pi only supports 8 bits (or 9 bits in LoSSI mode). Default = 0. 31 | bits_per_word: u8, 32 | // Set to 1 to briefly set SS inactive between this segment and the next. If this is the last segment, keep SS active. 33 | cs_change: u8, 34 | // Number of outgoing lines used for dual/quad SPI. Not supported on the Raspberry Pi. Default = 0. 35 | tx_nbits: u8, 36 | // Number of incoming lines used for dual/quad SPI. Not supported on the Raspberry Pi. Default = 0. 37 | rx_nbits: u8, 38 | // Padding. Set to 0 for forward compatibility. 39 | pad: u16, 40 | // Zero-sized variable used to link this struct to the read buffer lifetime. 41 | read_buffer_lifetime: marker::PhantomData<&'a mut [u8]>, 42 | // Zero-sized variable used to link this struct to the write buffer lifetime. 43 | write_buffer_lifetime: marker::PhantomData<&'b [u8]>, 44 | } 45 | 46 | impl<'a, 'b> Segment<'a, 'b> { 47 | /// Constructs a new `Segment` with the default settings, and configures it 48 | /// for a simultaneous (full-duplex) read/write transfer. 49 | /// 50 | /// For `Segment`s that only require either a read or write operation, call 51 | /// [`with_read`] or [`with_write`] instead of `new`. 52 | /// 53 | /// [`Spi::transfer_segments`] will only transfer as many bytes as the shortest of 54 | /// the two buffers contains. 55 | /// 56 | /// By default, all customizable settings are set to 0, which means it uses 57 | /// the same values as set for [`Spi`]. 58 | /// 59 | /// [`Spi::transfer_segments`]: struct.Spi.html#method.transfer_segments 60 | /// [`Spi`]: struct.Spi.html 61 | /// [`with_read`]: #method.with_read 62 | /// [`with_write`]: #method.with_write 63 | pub fn new(read_buffer: &'a mut [u8], write_buffer: &'b [u8]) -> Segment<'a, 'b> { 64 | Segment::with_settings(Some(read_buffer), Some(write_buffer), 0, 0, 0, false) 65 | } 66 | 67 | /// Constructs a new `Segment` with the default settings, and configures it 68 | /// for a read operation. 69 | /// 70 | /// Incoming data from the slave device is written to `buffer`. The total 71 | /// number of bytes read depends on the length of `buffer`. A zero-value 72 | /// byte is sent for every byte read. 73 | /// 74 | /// By default, all customizable settings are set to 0, which means it uses 75 | /// the same values as set for [`Spi`]. 76 | /// 77 | /// [`Spi`]: struct.Spi.html 78 | pub fn with_read(buffer: &mut [u8]) -> Segment<'_, '_> { 79 | Segment::with_settings(Some(buffer), None, 0, 0, 0, false) 80 | } 81 | 82 | /// Constructs a new `Segment` with the default settings, and configures it 83 | /// for a write operation. 84 | /// 85 | /// Outgoing data from `buffer` is sent to the slave device. Any 86 | /// incoming data is discarded. 87 | /// 88 | /// By default, all customizable settings are set to 0, which means it uses 89 | /// the same values as set for [`Spi`]. 90 | /// 91 | /// [`Spi`]: struct.Spi.html 92 | pub fn with_write(buffer: &[u8]) -> Segment<'_, '_> { 93 | Segment::with_settings(None, Some(buffer), 0, 0, 0, false) 94 | } 95 | 96 | /// Constructs a new `Segment` with the specified settings. 97 | /// 98 | /// These settings override the values set for [`Spi`], and are only used 99 | /// for this specific segment. 100 | /// 101 | /// If `read_buffer` is set to `None`, any incoming data is discarded. 102 | /// 103 | /// If `write_buffer` is set to `None`, a zero-value byte is sent for every 104 | /// byte read. 105 | /// 106 | /// If both `read_buffer` and `write_buffer` are specified, [`Spi::transfer_segments`] 107 | /// will only transfer as many bytes as the shortest of the two buffers contains. 108 | /// 109 | /// `clock_speed` sets a custom clock speed in hertz (Hz). 110 | /// 111 | /// `delay` sets a delay in microseconds (µs). 112 | /// 113 | /// `bits_per_word` sets the number of bits per word. The Raspberry Pi currently only supports 8 bits per word. 114 | /// 115 | /// `ss_change` changes how Slave Select behaves in between two segments (toggle SS), or after the final segment (keep SS active). 116 | /// 117 | /// [`Spi::transfer_segments`]: struct.Spi.html#method.transfer_segments 118 | /// [`Spi`]: struct.Spi.html 119 | pub fn with_settings( 120 | read_buffer: Option<&'a mut [u8]>, 121 | write_buffer: Option<&'b [u8]>, 122 | clock_speed: u32, 123 | delay: u16, 124 | bits_per_word: u8, 125 | ss_change: bool, 126 | ) -> Segment<'a, 'b> { 127 | // Len will contain the length of the shortest of the supplied buffers 128 | let mut len: u32 = 0; 129 | 130 | let tx_buf = if let Some(buffer) = write_buffer { 131 | len = buffer.len() as u32; 132 | buffer.as_ptr() as u64 133 | } else { 134 | 0 135 | }; 136 | 137 | let rx_buf = if let Some(buffer) = read_buffer { 138 | if (len > buffer.len() as u32) || tx_buf == 0 { 139 | len = buffer.len() as u32; 140 | } 141 | buffer.as_ptr() as u64 142 | } else { 143 | 0 144 | }; 145 | 146 | Segment { 147 | tx_buf, 148 | rx_buf, 149 | len, 150 | speed_hz: clock_speed, 151 | delay_usecs: delay, 152 | bits_per_word, 153 | cs_change: ss_change as u8, 154 | tx_nbits: 0, 155 | rx_nbits: 0, 156 | pad: 0, 157 | read_buffer_lifetime: marker::PhantomData, 158 | write_buffer_lifetime: marker::PhantomData, 159 | } 160 | } 161 | 162 | /// Returns the number of bytes that will be transferred. 163 | /// 164 | /// If both a read buffer and write buffer are supplied, 165 | /// [`Spi::transfer_segments`] only transfers as many bytes as the 166 | /// shortest of the two buffers contains. 167 | /// 168 | /// [`Spi::transfer_segments`]: struct.Spi.html#method.transfer_segments 169 | pub fn len(&self) -> usize { 170 | self.len as usize 171 | } 172 | 173 | /// Returns `true` if this segment won't transfer any bytes. 174 | pub fn is_empty(&self) -> bool { 175 | self.len == 0 176 | } 177 | 178 | /// Gets the custom clock speed in hertz (Hz) for this segment. 179 | pub fn clock_speed(&self) -> u32 { 180 | self.speed_hz 181 | } 182 | 183 | /// Sets a custom clock speed in hertz (Hz) for this segment. 184 | /// 185 | /// The SPI driver will automatically select the closest valid frequency. 186 | /// 187 | /// By default, `clock_speed` is set to `0`, which means 188 | /// it will use the same value as configured for [`Spi`]. 189 | /// 190 | /// [`Spi`]: struct.Spi.html 191 | pub fn set_clock_speed(&mut self, clock_speed: u32) { 192 | self.speed_hz = clock_speed; 193 | } 194 | 195 | /// Gets the delay in microseconds (µs) for this segment. 196 | pub fn delay(&self) -> u16 { 197 | self.delay_usecs 198 | } 199 | 200 | /// Sets a delay in microseconds (µs) for this segment. 201 | /// 202 | /// `set_delay` adds a delay at the end of this segment, 203 | /// before the (optional) Slave Select change. 204 | /// 205 | /// By default, `delay` is set to `0`. 206 | pub fn set_delay(&mut self, delay: u16) { 207 | self.delay_usecs = delay; 208 | } 209 | 210 | /// Gets the number of bits per word for this segment. 211 | pub fn bits_per_word(&self) -> u8 { 212 | self.bits_per_word 213 | } 214 | 215 | /// Sets the number of bits per word for this segment. 216 | /// 217 | /// The Raspberry Pi currently only supports 8 bit words. 218 | /// 219 | /// By default, `bits_per_word` is set to `0`, which means 220 | /// it will use the same value as configured for [`Spi`]. 221 | /// 222 | /// [`Spi`]: struct.Spi.html 223 | pub fn set_bits_per_word(&mut self, bits_per_word: u8) { 224 | self.bits_per_word = bits_per_word; 225 | } 226 | 227 | /// Gets the state of Slave Select change for this segment. 228 | pub fn ss_change(&self) -> bool { 229 | self.cs_change == 1 230 | } 231 | 232 | /// Changes Slave Select's behavior for this segment. 233 | /// 234 | /// If `ss_change` is set to `true`, and this is not the last 235 | /// segment of the transfer, the Slave Select line will briefly 236 | /// change to inactive between this segment and the next. 237 | /// If this is the last segment, setting `ss_change` to true will 238 | /// keep Slave Select active after the transfer ends. 239 | /// 240 | /// By default, `ss_change` is set to `false`. 241 | pub fn set_ss_change(&mut self, ss_change: bool) { 242 | self.cs_change = ss_change as u8; 243 | } 244 | } 245 | 246 | impl fmt::Debug for Segment<'_, '_> { 247 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 248 | f.debug_struct("Segment") 249 | .field("tx_buf", &self.tx_buf) 250 | .field("rx_buf", &self.rx_buf) 251 | .field("len", &self.len) 252 | .field("speed_hz", &self.speed_hz) 253 | .field("delay_usecs", &self.delay_usecs) 254 | .field("bits_per_word", &self.bits_per_word) 255 | .field("cs_change", &self.cs_change) 256 | .field("tx_nbits", &self.tx_nbits) 257 | .field("rx_nbits", &self.rx_nbits) 258 | .field("pad", &self.pad) 259 | .finish() 260 | } 261 | } 262 | -------------------------------------------------------------------------------- /src/gpio/gpiomem/rp1.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::fmt; 4 | use std::fs::OpenOptions; 5 | use std::io; 6 | use std::os::unix::fs::OpenOptionsExt; 7 | use std::os::unix::io::AsRawFd; 8 | use std::ptr; 9 | 10 | use libc::{self, c_void, size_t, MAP_FAILED, MAP_SHARED, O_SYNC, PROT_READ, PROT_WRITE}; 11 | 12 | use crate::gpio::{Bias, Error, Level, Mode, Result}; 13 | use crate::system::{DeviceInfo, SoC}; 14 | 15 | use super::GpioRegisters; 16 | 17 | const PATH_DEV_GPIOMEM: &str = "/dev/gpiomem0"; 18 | 19 | // Each register contains 32 bits 20 | const REG_SIZE: usize = std::mem::size_of::(); 21 | // gpiomem contains IO_BANK0-2, SYS_RIO0-2, PADS_BANK0-2, PADS_ETH 22 | const MEM_SIZE: usize = 0x30000; 23 | 24 | // We're only accessing the first 28 GPIOs. The rest is currently marked 25 | // as internal-use only, so we only need IO_BANK0/SYS_RIO0/PADS_BANK0. 26 | const IO_BANK0_OFFSET: usize = 0x00000; 27 | const SYS_RIO0_OFFSET: usize = 0x10000; 28 | const PADS_BANK0_OFFSET: usize = 0x20000; 29 | 30 | // Atomic register access (datasheet @ 2.4) 31 | const RW_OFFSET: usize = 0x0000; 32 | const XOR_OFFSET: usize = 0x1000; 33 | const SET_OFFSET: usize = 0x2000; 34 | const CLR_OFFSET: usize = 0x3000; 35 | 36 | // STATUS and CTRL offsets for the IO_BANK registers (datasheet @ 3.1.4) 37 | const GPIO_STATUS: usize = 0x0000; 38 | const GPIO_CTRL: usize = 0x0004; 39 | // Offset to the next GPIO for the IO_BANK registers (datasheet @ 3.1.4) 40 | const GPIO_OFFSET: usize = 8; 41 | 42 | const CTRL_FUNCSEL_MASK: u32 = 0x001f; 43 | const CTRL_FUNCSEL_LSB: u32 = 0; 44 | const CTRL_OUTOVER_MASK: u32 = 0x3000; 45 | const CTRL_OUTOVER_LSB: u32 = 12; 46 | const CTRL_OEOVER_MASK: u32 = 0xc000; 47 | const CTRL_OEOVER_LSB: u32 = 14; 48 | 49 | // Drive output from peripheral signal selected by FUNCSEL 50 | const OUTOVER_PERI: u32 = 0; 51 | // Drive output enable from peripheral signal selected by FUNCSEL 52 | const OEOVER_PERI: u32 = 0; 53 | 54 | // Function select modes 55 | const FSEL_ALT0: u8 = 0; 56 | const FSEL_ALT1: u8 = 1; 57 | const FSEL_ALT2: u8 = 2; 58 | const FSEL_ALT3: u8 = 3; 59 | const FSEL_ALT4: u8 = 4; 60 | const FSEL_ALT5: u8 = 5; // GPIO 61 | const FSEL_ALT6: u8 = 6; 62 | const FSEL_ALT7: u8 = 7; 63 | const FSEL_ALT8: u8 = 8; 64 | const FSEL_NULL: u8 = 31; 65 | 66 | // GPIO offset for the PADS_BANK registers (datasheet @ 3.1.4) 67 | const PADS_GPIO: usize = 0x04; 68 | // Offset to the next GPIO for the PADS_BANK registers (datasheet @ 3.1.4) 69 | const PADS_OFFSET: usize = 4; 70 | 71 | const PADS_IN_ENABLE_MASK: u32 = 0x40; 72 | const PADS_OUT_DISABLE_MASK: u32 = 0x80; 73 | 74 | const PADS_BIAS_MASK: u32 = 0x0c; 75 | const PADS_BIAS_LSB: u32 = 2; 76 | 77 | const PADS_BIAS_OFF: u32 = 0; 78 | const PADS_BIAS_DOWN: u32 = 1; 79 | const PADS_BIAS_UP: u32 = 2; 80 | 81 | // GPIO output drive 82 | const RIO_OUT: usize = 0x00; 83 | // GPIO output drive enable 84 | const RIO_OE: usize = 0x04; 85 | // GPIO input value 86 | const RIO_IN: usize = 0x08; 87 | 88 | pub struct GpioMem { 89 | mem_ptr: *mut u32, 90 | soc: SoC, 91 | } 92 | 93 | impl fmt::Debug for GpioMem { 94 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 95 | f.debug_struct("GpioMem") 96 | .field("mem_ptr", &self.mem_ptr) 97 | .field("soc", &self.soc) 98 | .finish() 99 | } 100 | } 101 | 102 | impl GpioMem { 103 | pub fn open() -> Result { 104 | let mem_ptr = Self::map_devgpiomem()?; 105 | 106 | // Identify which SoC we're using. 107 | let soc = DeviceInfo::new().map_err(|_| Error::UnknownModel)?.soc(); 108 | 109 | Ok(GpioMem { mem_ptr, soc }) 110 | } 111 | 112 | fn map_devgpiomem() -> Result<*mut u32> { 113 | // Open gpiomem with read/write/sync flags. This might fail if the 114 | // gpiomem cdev doesn't exist (< Raspbian Jessie), or gpiomem 115 | // doesn't have the appropriate permissions, or the current user is 116 | // not a member of the gpio group. 117 | let gpiomem_file = OpenOptions::new() 118 | .read(true) 119 | .write(true) 120 | .custom_flags(O_SYNC) 121 | .open(PATH_DEV_GPIOMEM)?; 122 | 123 | // Memory-map gpiomem at offset 0 124 | let gpiomem_ptr = unsafe { 125 | libc::mmap( 126 | ptr::null_mut(), 127 | MEM_SIZE, 128 | PROT_READ | PROT_WRITE, 129 | MAP_SHARED, 130 | gpiomem_file.as_raw_fd(), 131 | 0, 132 | ) 133 | }; 134 | 135 | if gpiomem_ptr == MAP_FAILED { 136 | return Err(Error::Io(io::Error::last_os_error())); 137 | } 138 | 139 | Ok(gpiomem_ptr as *mut u32) 140 | } 141 | 142 | #[inline(always)] 143 | fn read(&self, offset: usize) -> u32 { 144 | unsafe { ptr::read_volatile(self.mem_ptr.add(offset)) } 145 | } 146 | 147 | #[inline(always)] 148 | fn write(&self, offset: usize, value: u32) { 149 | unsafe { 150 | ptr::write_volatile(self.mem_ptr.add(offset), value); 151 | } 152 | } 153 | 154 | fn direction(&self, pin: u8) -> Mode { 155 | let offset = (SYS_RIO0_OFFSET + RIO_OE) / REG_SIZE; 156 | let reg_value = (self.read(offset) >> pin) as u8 & 0b1; 157 | 158 | if reg_value > 0 { 159 | Mode::Output 160 | } else { 161 | Mode::Input 162 | } 163 | } 164 | 165 | fn set_direction(&self, pin: u8, mode: Mode) { 166 | let offset = match mode { 167 | Mode::Output => (SYS_RIO0_OFFSET + RIO_OE + SET_OFFSET) / REG_SIZE, 168 | _ => (SYS_RIO0_OFFSET + RIO_OE + CLR_OFFSET) / REG_SIZE, 169 | }; 170 | 171 | self.write(offset, 1 << pin); 172 | } 173 | 174 | fn input_enable(&self, pin: u8) { 175 | let offset = 176 | (PADS_BANK0_OFFSET + PADS_GPIO + (pin as usize * PADS_OFFSET) + SET_OFFSET) / REG_SIZE; 177 | 178 | self.write(offset, PADS_IN_ENABLE_MASK); 179 | } 180 | 181 | fn input_disable(&self, pin: u8) { 182 | let offset = 183 | (PADS_BANK0_OFFSET + PADS_GPIO + (pin as usize * PADS_OFFSET) + CLR_OFFSET) / REG_SIZE; 184 | 185 | self.write(offset, PADS_IN_ENABLE_MASK); 186 | } 187 | 188 | fn output_enable(&self, pin: u8) { 189 | let offset = 190 | (PADS_BANK0_OFFSET + PADS_GPIO + (pin as usize * PADS_OFFSET) + CLR_OFFSET) / REG_SIZE; 191 | 192 | self.write(offset, PADS_OUT_DISABLE_MASK); 193 | } 194 | 195 | fn output_disable(&self, pin: u8) { 196 | let offset = 197 | (PADS_BANK0_OFFSET + PADS_GPIO + (pin as usize * PADS_OFFSET) + SET_OFFSET) / REG_SIZE; 198 | 199 | self.write(offset, PADS_OUT_DISABLE_MASK); 200 | } 201 | } 202 | 203 | impl GpioRegisters for GpioMem { 204 | #[inline(always)] 205 | fn set_high(&self, pin: u8) { 206 | let offset = (SYS_RIO0_OFFSET + RIO_OUT + SET_OFFSET) / REG_SIZE; 207 | 208 | self.write(offset, 1 << pin); 209 | } 210 | 211 | #[inline(always)] 212 | fn set_low(&self, pin: u8) { 213 | let offset = (SYS_RIO0_OFFSET + RIO_OUT + CLR_OFFSET) / REG_SIZE; 214 | 215 | self.write(offset, 1 << pin); 216 | } 217 | 218 | #[inline(always)] 219 | fn level(&self, pin: u8) -> Level { 220 | let offset = (SYS_RIO0_OFFSET + RIO_IN) / REG_SIZE; 221 | let reg_value = self.read(offset); 222 | 223 | unsafe { std::mem::transmute((reg_value >> pin) as u8 & 0b1) } 224 | } 225 | 226 | fn mode(&self, pin: u8) -> Mode { 227 | let offset = 228 | (IO_BANK0_OFFSET + GPIO_CTRL + (pin as usize * GPIO_OFFSET) + RW_OFFSET) / REG_SIZE; 229 | let reg_value = self.read(offset); 230 | 231 | match (reg_value & CTRL_FUNCSEL_MASK) as u8 { 232 | FSEL_ALT0 => Mode::Alt0, 233 | FSEL_ALT1 => Mode::Alt1, 234 | FSEL_ALT2 => Mode::Alt2, 235 | FSEL_ALT3 => Mode::Alt3, 236 | FSEL_ALT4 => Mode::Alt4, 237 | FSEL_ALT5 => self.direction(pin), // GPIO 238 | FSEL_ALT6 => Mode::Alt6, 239 | FSEL_ALT7 => Mode::Alt7, 240 | FSEL_ALT8 => Mode::Alt8, 241 | FSEL_NULL => Mode::Null, 242 | _ => Mode::Input, 243 | } 244 | } 245 | 246 | fn set_mode(&self, pin: u8, mode: Mode) { 247 | if mode == Mode::Null { 248 | self.input_disable(pin); 249 | self.output_disable(pin); 250 | } else { 251 | self.input_enable(pin); 252 | self.output_enable(pin); 253 | } 254 | 255 | let offset = 256 | (IO_BANK0_OFFSET + GPIO_CTRL + (pin as usize * GPIO_OFFSET) + RW_OFFSET) / REG_SIZE; 257 | let mut reg_value = self.read(offset); 258 | 259 | let fsel_mode = match mode { 260 | Mode::Input => FSEL_ALT5, // GPIO 261 | Mode::Output => FSEL_ALT5, // GPIO 262 | Mode::Alt0 => FSEL_ALT0, 263 | Mode::Alt1 => FSEL_ALT1, 264 | Mode::Alt2 => FSEL_ALT2, 265 | Mode::Alt3 => FSEL_ALT3, 266 | Mode::Alt4 => FSEL_ALT4, 267 | Mode::Alt5 => FSEL_ALT5, 268 | Mode::Alt6 => FSEL_ALT6, 269 | Mode::Alt7 => FSEL_ALT7, 270 | Mode::Alt8 => FSEL_ALT8, 271 | Mode::Null => FSEL_NULL, 272 | }; 273 | 274 | // Set the actual direction here, since this isn't set in the FSEL register. 275 | if mode == Mode::Input || mode == Mode::Output { 276 | self.set_direction(pin, mode); 277 | } 278 | 279 | reg_value = (reg_value & !CTRL_OUTOVER_MASK) | (OUTOVER_PERI << CTRL_OUTOVER_LSB); 280 | reg_value = (reg_value & !CTRL_OEOVER_MASK) | (OEOVER_PERI << CTRL_OEOVER_LSB); 281 | reg_value = (reg_value & !CTRL_FUNCSEL_MASK) | ((fsel_mode as u32) << CTRL_FUNCSEL_LSB); 282 | 283 | self.write(offset, reg_value); 284 | } 285 | 286 | fn set_bias(&self, pin: u8, bias: Bias) { 287 | let offset = 288 | (PADS_BANK0_OFFSET + PADS_GPIO + (pin as usize * PADS_OFFSET) + RW_OFFSET) / REG_SIZE; 289 | let mut reg_value = self.read(offset); 290 | 291 | reg_value = match bias { 292 | Bias::Off => (reg_value & !PADS_BIAS_MASK) | (PADS_BIAS_OFF << PADS_BIAS_LSB), 293 | Bias::PullDown => (reg_value & !PADS_BIAS_MASK) | (PADS_BIAS_DOWN << PADS_BIAS_LSB), 294 | Bias::PullUp => (reg_value & !PADS_BIAS_MASK) | (PADS_BIAS_UP << PADS_BIAS_LSB), 295 | }; 296 | 297 | self.write(offset, reg_value); 298 | } 299 | } 300 | 301 | impl Drop for GpioMem { 302 | fn drop(&mut self) { 303 | unsafe { 304 | libc::munmap(self.mem_ptr as *mut c_void, MEM_SIZE as size_t); 305 | } 306 | } 307 | } 308 | 309 | // Required because of the raw pointer to our memory-mapped file 310 | unsafe impl Send for GpioMem {} 311 | 312 | unsafe impl Sync for GpioMem {} 313 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RPPAL - Raspberry Pi Peripheral Access Library 2 | 3 | [![Build status](https://github.com/golemparts/rppal/actions/workflows/ci.yml/badge.svg)](https://github.com/golemparts/rppal/actions/workflows/ci.yml) 4 | [![Latest release](https://img.shields.io/crates/v/rppal)](https://crates.io/crates/rppal) 5 | [![Minimum rustc version](https://img.shields.io/badge/rustc-v1.60.0-lightgray.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) 6 | [![Documentation](https://docs.rs/rppal/badge.svg)](https://docs.rs/rppal) 7 | [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 8 | 9 | ## This project is no longer maintained 10 | 11 | As of July 1, 2025, I have decided to retire RPPAL. This means: 12 | * No new features will be added. 13 | * Bug fixes will no longer be provided. 14 | * Support for new hardware is not planned. 15 | * Pull requests and issues will no longer be reviewed or addressed. 16 | 17 | I want to express my sincere gratitude to everyone who contributed to, used, and supported RPPAL over the years. Your contributions and feedback were invaluable. 18 | 19 | ### Why have I retired RPPAL? 20 | 21 | RPPAL began as a passion project in 2016, nearly nine years ago, when I first started working with electronics and needed a project to work on my Rust skills. Initially, it started out as the GPIO module of my Blinkt library. As the scope expanded to include support for other peripherals, RPPAL was spun off into its own distinct project, evolving into the comprehensive library it is today. 22 | 23 | However, over the past several years, my personal interests and professional focus have shifted away from electronics. As a result, I haven't actively used RPPAL myself for quite some time. I no longer have a dedicated hardware test setup, nor do I plan on adding new Raspberry Pi models to my collection. This makes it impractical to thoroughly test changes or ensure compatibility with new hardware releases. 24 | 25 | Maintaining a project requires significant dedication, and without active personal use or the necessary testing environment, it's become challenging to provide the level of attention this project deserves. 26 | 27 | ### What does this mean for you? 28 | 29 | You are welcome to continue using RPPAL. However, please be aware you will not receive any further updates or support. RPPAL works with all Raspberry Pi models released before July 1, 2025, up to and including the Raspberry Pi 5. 30 | 31 | #### Forking the project 32 | 33 | If you wish to continue its development, you may fork this project under the terms and conditions of the MIT License. 34 | 35 | ## RPPAL 36 | 37 | RPPAL provides access to the Raspberry Pi's GPIO, I2C, PWM, SPI and UART peripherals through a user-friendly interface. In addition to peripheral access, RPPAL also offers support for USB to serial adapters. 38 | 39 | The library can be used in conjunction with a variety of platform-agnostic drivers through its `embedded-hal` trait implementations. Both `embedded-hal` v0.2.7 and v1 are supported. 40 | 41 | RPPAL requires a recent release of Raspberry Pi OS. Similar Linux distributions may work, but are unsupported. Both GNU and musl `libc` targets are supported. RPPAL is compatible with the Raspberry Pi A, A+, B, B+, 2B, 3A+, 3B, 3B+, 4B, 5, CM, CM 3, CM 3+, CM 4, CM 5, CM 5 Lite, 400, 500, Zero, Zero W and Zero 2 W. 42 | 43 | ## Table of contents 44 | 45 | - [Usage](#usage) 46 | - [Examples](#examples) 47 | - [Optional features](#optional-features) 48 | - [Supported peripherals](#supported-peripherals) 49 | - [GPIO](#gpio) 50 | - [I2C](#i2c) 51 | - [PWM](#pwm) 52 | - [SPI](#spi) 53 | - [UART](#uart) 54 | - [Cross compilation](#cross-compilation) 55 | - [Cargo](#cargo) 56 | - [Visual Studio Code](#visual-studio-code) 57 | - [Caution](#caution) 58 | - [Copyright and license](#copyright-and-license) 59 | 60 | ## Usage 61 | 62 | Add a dependency for `rppal` to your `Cargo.toml` using `cargo add rppal`, or by adding the following line to your dependencies section. 63 | 64 | ```toml 65 | [dependencies] 66 | rppal = "0.22.1" 67 | ``` 68 | 69 | If your project requires `embedded-hal` trait implementations, specify either the `hal` or `hal-unproven` feature flag in the dependency declaration. 70 | 71 | ```toml 72 | [dependencies] 73 | rppal = { version = "0.22.1", features = ["hal"] } 74 | ``` 75 | 76 | Call `new()` on any of the peripherals to construct a new instance. 77 | 78 | ```rust 79 | use rppal::gpio::Gpio; 80 | use rppal::i2c::I2c; 81 | use rppal::pwm::{Channel, Pwm}; 82 | use rppal::spi::{Bus, Mode, SlaveSelect, Spi}; 83 | use rppal::uart::{Parity, Uart}; 84 | 85 | let gpio = Gpio::new()?; 86 | let i2c = I2c::new()?; 87 | let pwm = Pwm::new(Channel::Pwm0)?; 88 | let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 16_000_000, Mode::Mode0)?; 89 | let uart = Uart::new(115_200, Parity::None, 8, 1)?; 90 | ``` 91 | 92 | Access to some peripherals may need to be enabled first through `sudo raspi-config` or by editing `/boot/firmware/config.txt`. Refer to the relevant module's documentation for any required steps. 93 | 94 | ## Examples 95 | 96 | This example demonstrates how to blink an LED connected to a GPIO pin. Remember to add a resistor of an appropriate value in series, to prevent exceeding the maximum current rating of the GPIO pin and the LED. 97 | 98 | ```rust 99 | use std::error::Error; 100 | use std::thread; 101 | use std::time::Duration; 102 | 103 | use rppal::gpio::Gpio; 104 | use rppal::system::DeviceInfo; 105 | 106 | // Gpio uses BCM pin numbering. BCM GPIO 23 is tied to physical pin 16. 107 | const GPIO_LED: u8 = 23; 108 | 109 | fn main() -> Result<(), Box> { 110 | println!("Blinking an LED on a {}.", DeviceInfo::new()?.model()); 111 | 112 | let mut pin = Gpio::new()?.get(GPIO_LED)?.into_output(); 113 | 114 | // Blink the LED by setting the pin's logic level high for 500 ms. 115 | pin.set_high(); 116 | thread::sleep(Duration::from_millis(500)); 117 | pin.set_low(); 118 | 119 | Ok(()) 120 | } 121 | ``` 122 | 123 | Additional examples can be found in the `examples` directory. 124 | 125 | ## Optional features 126 | 127 | By default, all optional features are disabled. You can enable a feature by specifying the relevant feature flag(s) in the dependency declaration for `rppal` in your `Cargo.toml`. 128 | 129 | * `hal` - Enables `embedded-hal` trait implementations for all supported peripherals. This doesn't include `unproven` traits. 130 | * `hal-unproven` - Enables `embedded-hal` trait implementations for all supported peripherals, including traits marked as `unproven`. Note that `embedded-hal`'s `unproven` traits don't follow semver rules. Patch releases may introduce breaking changes. 131 | 132 | ## Supported peripherals 133 | 134 | ### [GPIO](https://docs.rs/rppal/latest/rppal/gpio) 135 | 136 | To ensure fast performance, RPPAL controls the GPIO peripheral by directly accessing the registers through either `/dev/gpiomem` or `/dev/mem`. GPIO interrupts are configured using the `gpiochip` character device. 137 | 138 | #### Features 139 | 140 | * Get/set pin mode and logic level 141 | * Configure built-in pull-up/pull-down resistors 142 | * Synchronous and asynchronous interrupt handlers 143 | * Software-based PWM implementation 144 | * Optional `embedded-hal` trait implementations 145 | 146 | ### [I2C](https://docs.rs/rppal/latest/rppal/i2c) 147 | 148 | The Broadcom Serial Controller (BSC) peripheral controls a proprietary bus compliant with the I2C bus/interface. RPPAL communicates with the BSC using the `i2cdev` character device. 149 | 150 | #### Features 151 | 152 | * Single master, 7-bit slave addresses, transfer rates up to 400 kbit/s (Fast-mode) 153 | * I2C basic read/write, block read/write, combined write+read 154 | * SMBus protocols: Quick Command, Send/Receive Byte, Read/Write Byte/Word, Process Call, Block Write, PEC 155 | * Optional `embedded-hal` trait implementations 156 | 157 | ### [PWM](https://docs.rs/rppal/latest/rppal/pwm) 158 | 159 | RPPAL controls the Raspberry Pi's PWM peripheral through the `pwm` sysfs interface. 160 | 161 | #### Features 162 | 163 | * Up to four hardware PWM channels depending on the Raspberry Pi model 164 | * Configurable frequency, duty cycle and polarity 165 | * Optional `embedded-hal` trait implementations 166 | 167 | ### [SPI](https://docs.rs/rppal/latest/rppal/spi) 168 | 169 | RPPAL controls the Raspberry Pi's main and auxiliary SPI peripherals through the `spidev` character device. 170 | 171 | #### Features 172 | 173 | * SPI master, mode 0-3, Slave Select active-low/active-high, 8 bits per word, configurable clock speed 174 | * Half-duplex reads, writes, and multi-segment transfers 175 | * Full-duplex transfers and multi-segment transfers 176 | * Customizable options for each segment in a multi-segment transfer (clock speed, delay, SS change) 177 | * Reverse bit order helper function 178 | * Optional `embedded-hal` trait implementations 179 | 180 | ### [UART](https://docs.rs/rppal/latest/rppal/uart) 181 | 182 | RPPAL controls the Raspberry Pi's UART peripherals through the `ttyAMA0` (PL011) and `ttyS0` (mini UART) character devices. USB to serial adapters are controlled using the `ttyUSBx` and `ttyACMx` character devices. 183 | 184 | #### Features 185 | 186 | * Support for UART peripherals (PL011, mini UART) and USB to serial adapters 187 | * None/Even/Odd/Mark/Space parity, 5-8 data bits, 1-2 stop bits 188 | * Transfer rates up to 4 Mbit/s (device-dependent) 189 | * XON/XOFF software flow control 190 | * RTS/CTS hardware flow control with automatic pin configuration 191 | * Optional `embedded-hal` trait implementations 192 | 193 | ## Cross compilation 194 | 195 | If you're not working directly on a Raspberry Pi, you'll have to cross-compile your code for the appropriate ARM architecture. Check out [this guide](https://github.com/japaric/rust-cross) for more information, or try the [cross](https://github.com/japaric/cross) project for "zero setup" cross compilation. 196 | 197 | ### Cargo 198 | 199 | For manual cross-compilation without the use of `cross`, you will need to install the appropriate target. Most Raspberry Pi models either need the `armv7-unknown-linux-gnueabihf` target for 32-bit Linux distributions, or `aarch64-unknown-linux-gnu` for 64-bit. For some models, like the Raspberry Pi Zero, a different target triple is required. 200 | 201 | Install the relevant target using `rustup`. 202 | 203 | ```bash 204 | rustup target install armv7-unknown-linux-gnueabihf 205 | ``` 206 | 207 | In the root directory of your project, create a `.cargo` subdirectory, and save the following snippet to `.cargo/config.toml`. 208 | 209 | ```toml 210 | [build] 211 | target = "armv7-unknown-linux-gnueabihf" 212 | ``` 213 | 214 | ### Visual Studio Code 215 | 216 | The rust-analyzer extension for Visual Studio Code needs to be made aware of the target platform by setting the `rust-analyzer.cargo.target` configuration option. In the root directory of your project, create a `.vscode` subdirectory, and then save the following snippet to `.vscode/settings.json`. 217 | 218 | ```json 219 | { 220 | "rust-analyzer.cargo.target": "armv7-unknown-linux-gnueabihf" 221 | } 222 | ``` 223 | 224 | ## Caution 225 | 226 | Always be careful when working with the Raspberry Pi's peripherals, especially if you attach any external components to the GPIO pins. Improper use can lead to permanent damage. 227 | 228 | ## Copyright and license 229 | 230 | Copyright (c) 2017-2025 Rene van der Meer. Released under the [MIT license](LICENSE). 231 | -------------------------------------------------------------------------------- /src/gpio/gpiomem/bcm.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::fs::OpenOptions; 3 | use std::io; 4 | use std::os::unix::fs::OpenOptionsExt; 5 | use std::os::unix::io::AsRawFd; 6 | use std::ptr; 7 | use std::sync::atomic::{AtomicBool, Ordering}; 8 | use std::thread; 9 | use std::time::Duration; 10 | 11 | use libc::{self, c_void, off_t, size_t, MAP_FAILED, MAP_SHARED, O_SYNC, PROT_READ, PROT_WRITE}; 12 | 13 | use crate::gpio::gpiomem::GpioRegisters; 14 | use crate::gpio::{Bias, Error, Level, Mode, Result}; 15 | use crate::system::{DeviceInfo, SoC}; 16 | 17 | const PATH_DEV_GPIOMEM: &str = "/dev/gpiomem"; 18 | const PATH_DEV_MEM: &str = "/dev/mem"; 19 | // The BCM2835 has 41 32-bit registers related to the GPIO (datasheet @ 6.1). 20 | // The BCM2711 (RPi4) has GPIO-related 32-bit registers #0 .. #60, an address space of 61 registers (datasheet @ 5.1). 21 | const GPIO_MEM_REGISTERS: usize = 61; 22 | const GPIO_MEM_SIZE: usize = GPIO_MEM_REGISTERS * std::mem::size_of::(); 23 | const GPFSEL0: usize = 0x00; 24 | const GPSET0: usize = 0x1c / std::mem::size_of::(); 25 | const GPCLR0: usize = 0x28 / std::mem::size_of::(); 26 | const GPLEV0: usize = 0x34 / std::mem::size_of::(); 27 | const GPPUD: usize = 0x94 / std::mem::size_of::(); 28 | const GPPUDCLK0: usize = 0x98 / std::mem::size_of::(); 29 | // Only available on BCM2711 (RPi4) 30 | const GPPUD_CNTRL_REG0: usize = 0xe4 / std::mem::size_of::(); 31 | 32 | const FSEL_INPUT: u8 = 0b000; 33 | const FSEL_OUTPUT: u8 = 0b001; 34 | const FSEL_ALT0: u8 = 0b100; 35 | const FSEL_ALT1: u8 = 0b101; 36 | const FSEL_ALT2: u8 = 0b110; 37 | const FSEL_ALT3: u8 = 0b111; 38 | const FSEL_ALT4: u8 = 0b011; 39 | const FSEL_ALT5: u8 = 0b010; 40 | 41 | pub struct GpioMem { 42 | mem_ptr: *mut u32, 43 | locks: [AtomicBool; GPIO_MEM_REGISTERS], 44 | soc: SoC, 45 | } 46 | 47 | impl fmt::Debug for GpioMem { 48 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 49 | f.debug_struct("GpioMem") 50 | .field("mem_ptr", &self.mem_ptr) 51 | .field("locks", &format_args!("{{ .. }}")) 52 | .field("soc", &self.soc) 53 | .finish() 54 | } 55 | } 56 | 57 | impl GpioMem { 58 | pub fn open() -> Result { 59 | // Try /dev/gpiomem first. If that fails, try /dev/mem instead. If neither works, 60 | // report back the error that's the most relevant. 61 | let mem_ptr = match Self::map_devgpiomem() { 62 | Ok(ptr) => ptr, 63 | Err(gpiomem_err) => match Self::map_devmem() { 64 | Ok(ptr) => ptr, 65 | Err(Error::Io(ref e)) if e.kind() == io::ErrorKind::PermissionDenied => { 66 | // Did /dev/gpiomem also give us a Permission Denied error? If so, return 67 | // that path instead of /dev/mem. Solving /dev/gpiomem issues should be 68 | // preferred (add user to gpio group) over /dev/mem (use sudo), 69 | match gpiomem_err { 70 | Error::Io(ref e) if e.kind() == io::ErrorKind::PermissionDenied => { 71 | return Err(Error::PermissionDenied(String::from(PATH_DEV_GPIOMEM))); 72 | } 73 | _ => return Err(Error::PermissionDenied(String::from(PATH_DEV_MEM))), 74 | } 75 | } 76 | Err(Error::UnknownModel) => return Err(Error::UnknownModel), 77 | _ => return Err(gpiomem_err), 78 | }, 79 | }; 80 | 81 | let locks = init_array!(AtomicBool::new(false), GPIO_MEM_REGISTERS); 82 | 83 | // Identify which SoC we're using. 84 | let soc = DeviceInfo::new().map_err(|_| Error::UnknownModel)?.soc(); 85 | 86 | Ok(GpioMem { 87 | mem_ptr, 88 | locks, 89 | soc, 90 | }) 91 | } 92 | 93 | fn map_devgpiomem() -> Result<*mut u32> { 94 | // Open /dev/gpiomem with read/write/sync flags. This might fail if 95 | // /dev/gpiomem doesn't exist (< Raspbian Jessie), or /dev/gpiomem 96 | // doesn't have the appropriate permissions, or the current user is 97 | // not a member of the gpio group. 98 | let gpiomem_file = OpenOptions::new() 99 | .read(true) 100 | .write(true) 101 | .custom_flags(O_SYNC) 102 | .open(PATH_DEV_GPIOMEM)?; 103 | 104 | // Memory-map /dev/gpiomem at offset 0 105 | let gpiomem_ptr = unsafe { 106 | libc::mmap( 107 | ptr::null_mut(), 108 | GPIO_MEM_SIZE, 109 | PROT_READ | PROT_WRITE, 110 | MAP_SHARED, 111 | gpiomem_file.as_raw_fd(), 112 | 0, 113 | ) 114 | }; 115 | 116 | if gpiomem_ptr == MAP_FAILED { 117 | return Err(Error::Io(io::Error::last_os_error())); 118 | } 119 | 120 | Ok(gpiomem_ptr as *mut u32) 121 | } 122 | 123 | fn map_devmem() -> Result<*mut u32> { 124 | // Identify which SoC we're using, so we know what offset to start at 125 | let device_info = DeviceInfo::new().map_err(|_| Error::UnknownModel)?; 126 | 127 | let mem_file = OpenOptions::new() 128 | .read(true) 129 | .write(true) 130 | .custom_flags(O_SYNC) 131 | .open(PATH_DEV_MEM)?; 132 | 133 | // Memory-map /dev/mem at the appropriate offset for our SoC 134 | let mem_ptr = unsafe { 135 | libc::mmap( 136 | ptr::null_mut(), 137 | GPIO_MEM_SIZE, 138 | PROT_READ | PROT_WRITE, 139 | MAP_SHARED, 140 | mem_file.as_raw_fd(), 141 | (device_info.peripheral_base() + device_info.gpio_offset()) as off_t, 142 | ) 143 | }; 144 | 145 | if mem_ptr == MAP_FAILED { 146 | return Err(Error::Io(io::Error::last_os_error())); 147 | } 148 | 149 | Ok(mem_ptr as *mut u32) 150 | } 151 | 152 | #[inline(always)] 153 | fn read(&self, offset: usize) -> u32 { 154 | unsafe { ptr::read_volatile(self.mem_ptr.add(offset)) } 155 | } 156 | 157 | #[inline(always)] 158 | fn write(&self, offset: usize, value: u32) { 159 | unsafe { 160 | ptr::write_volatile(self.mem_ptr.add(offset), value); 161 | } 162 | } 163 | } 164 | 165 | impl Drop for GpioMem { 166 | fn drop(&mut self) { 167 | unsafe { 168 | libc::munmap(self.mem_ptr as *mut c_void, GPIO_MEM_SIZE as size_t); 169 | } 170 | } 171 | } 172 | 173 | impl GpioRegisters for GpioMem { 174 | #[inline(always)] 175 | fn set_high(&self, pin: u8) { 176 | let offset = GPSET0 + pin as usize / 32; 177 | let shift = pin % 32; 178 | 179 | self.write(offset, 1 << shift); 180 | } 181 | 182 | #[inline(always)] 183 | fn set_low(&self, pin: u8) { 184 | let offset = GPCLR0 + pin as usize / 32; 185 | let shift = pin % 32; 186 | 187 | self.write(offset, 1 << shift); 188 | } 189 | 190 | #[inline(always)] 191 | fn level(&self, pin: u8) -> Level { 192 | let offset = GPLEV0 + pin as usize / 32; 193 | let shift = pin % 32; 194 | let reg_value = self.read(offset); 195 | 196 | unsafe { std::mem::transmute((reg_value >> shift) as u8 & 0b1) } 197 | } 198 | 199 | fn mode(&self, pin: u8) -> Mode { 200 | let offset = GPFSEL0 + pin as usize / 10; 201 | let shift = (pin % 10) * 3; 202 | let reg_value = self.read(offset); 203 | 204 | match (reg_value >> shift) as u8 & 0b111 { 205 | FSEL_INPUT => Mode::Input, 206 | FSEL_OUTPUT => Mode::Output, 207 | FSEL_ALT0 => Mode::Alt0, 208 | FSEL_ALT1 => Mode::Alt1, 209 | FSEL_ALT2 => Mode::Alt2, 210 | FSEL_ALT3 => Mode::Alt3, 211 | FSEL_ALT4 => Mode::Alt4, 212 | FSEL_ALT5 => Mode::Alt5, 213 | _ => Mode::Input, 214 | } 215 | } 216 | 217 | fn set_mode(&self, pin: u8, mode: Mode) { 218 | let offset = GPFSEL0 + pin as usize / 10; 219 | let shift = (pin % 10) * 3; 220 | 221 | loop { 222 | if self.locks[offset] 223 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 224 | .is_ok() 225 | { 226 | break; 227 | } 228 | } 229 | 230 | let fsel_mode = match mode { 231 | Mode::Input => FSEL_INPUT, 232 | Mode::Output => FSEL_OUTPUT, 233 | Mode::Alt0 => FSEL_ALT0, 234 | Mode::Alt1 => FSEL_ALT1, 235 | Mode::Alt2 => FSEL_ALT2, 236 | Mode::Alt3 => FSEL_ALT3, 237 | Mode::Alt4 => FSEL_ALT4, 238 | Mode::Alt5 => FSEL_ALT5, 239 | _ => FSEL_INPUT, 240 | }; 241 | 242 | let reg_value = self.read(offset); 243 | self.write( 244 | offset, 245 | (reg_value & !(0b111 << shift)) | ((fsel_mode as u32) << shift), 246 | ); 247 | 248 | self.locks[offset].store(false, Ordering::SeqCst); 249 | } 250 | 251 | fn set_bias(&self, pin: u8, bias: Bias) { 252 | // Offset for register. 253 | let offset: usize; 254 | // Bit shift for pin position within register value. 255 | let shift: u8; 256 | 257 | // BCM2711 (RPi4) and BCM2712 (RPi5) need special handling. 258 | if self.soc == SoC::Bcm2711 || self.soc == SoC::Bcm2712 { 259 | offset = GPPUD_CNTRL_REG0 + pin as usize / 16; 260 | shift = pin % 16 * 2; 261 | 262 | // Index for lock is different than register. 263 | let lock = GPPUD_CNTRL_REG0 + pin as usize / 32; 264 | 265 | // Pull up vs pull down has a reverse bit pattern on BCM2711 vs others. 266 | let pud = match bias { 267 | Bias::Off => 0b00u32, 268 | Bias::PullDown => 0b10, 269 | Bias::PullUp => 0b01, 270 | }; 271 | 272 | loop { 273 | if self.locks[lock] 274 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 275 | .is_ok() 276 | { 277 | break; 278 | } 279 | } 280 | 281 | let reg_value = self.read(offset); 282 | self.write(offset, (reg_value & !(0b11 << shift)) | (pud << shift)); 283 | 284 | self.locks[lock].store(false, Ordering::SeqCst); 285 | } else { 286 | offset = GPPUDCLK0 + pin as usize / 32; 287 | shift = pin % 32; 288 | 289 | loop { 290 | if self.locks[GPPUD] 291 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 292 | .is_ok() 293 | { 294 | if self.locks[offset] 295 | .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) 296 | .is_ok() 297 | { 298 | break; 299 | } else { 300 | self.locks[GPPUD].store(false, Ordering::SeqCst); 301 | } 302 | } 303 | } 304 | 305 | // Set the control signal in GPPUD. 306 | let reg_value = self.read(GPPUD); 307 | self.write(GPPUD, (reg_value & !0b11) | ((bias as u32) & 0b11)); 308 | 309 | // The datasheet mentions waiting at least 150 cycles for set-up and hold, but 310 | // doesn't state which clock is used. This is likely the VPU clock (see 311 | // https://www.raspberrypi.org/forums/viewtopic.php?f=72&t=163352). At either 312 | // 250MHz or 400MHz, a 5µs delay + overhead is more than adequate. 313 | 314 | // Set-up time for the control signal. >= 5µs 315 | thread::sleep(Duration::new(0, 5000)); 316 | // Clock the control signal into the selected pin. 317 | self.write(offset, 1 << shift); 318 | 319 | // Hold time for the control signal. >= 5µs 320 | thread::sleep(Duration::new(0, 5000)); 321 | // Remove the control signal and clock. 322 | self.write(GPPUD, reg_value & !0b11); 323 | self.write(offset, 0); 324 | 325 | self.locks[offset].store(false, Ordering::SeqCst); 326 | self.locks[GPPUD].store(false, Ordering::SeqCst); 327 | } 328 | } 329 | } 330 | 331 | // Required because of the raw pointer to our memory-mapped file 332 | unsafe impl Send for GpioMem {} 333 | 334 | unsafe impl Sync for GpioMem {} 335 | -------------------------------------------------------------------------------- /src/uart/termios.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | 3 | use std::io; 4 | use std::time::Duration; 5 | 6 | use libc::{c_int, termios}; 7 | use libc::{B0, B110, B134, B150, B200, B300, B50, B75}; 8 | use libc::{B1000000, B1152000, B460800, B500000, B576000, B921600}; 9 | use libc::{B115200, B19200, B230400, B38400, B57600}; 10 | use libc::{B1200, B1800, B2400, B4800, B600, B9600}; 11 | use libc::{B1500000, B2000000, B2500000, B3000000, B3500000, B4000000}; 12 | use libc::{CLOCAL, CMSPAR, CREAD, CRTSCTS, TCSANOW}; 13 | use libc::{CS5, CS6, CS7, CS8, CSIZE, CSTOPB, PARENB, PARODD}; 14 | use libc::{IGNPAR, INPCK, PARMRK}; 15 | use libc::{IXANY, IXOFF, IXON, TCIFLUSH, TCIOFLUSH, TCOFLUSH, VMIN, VSTART, VSTOP, VTIME}; 16 | use libc::{TCIOFF, TCION, TIOCMGET, TIOCM_CTS, TIOCM_DSR, TIOCM_DTR, TIOCM_RTS}; 17 | use libc::{TIOCINQ, TIOCMBIC, TIOCMBIS, TIOCM_CAR, TIOCM_RNG, TIOCOUTQ}; 18 | 19 | use crate::uart::{Error, Parity, ParityCheck, Queue, Result}; 20 | 21 | const XON: u8 = 17; 22 | const XOFF: u8 = 19; 23 | 24 | #[cfg(target_env = "gnu")] 25 | pub fn attributes(fd: c_int) -> Result { 26 | let mut attr = termios { 27 | c_iflag: 0, 28 | c_oflag: 0, 29 | c_cflag: 0, 30 | c_lflag: 0, 31 | c_line: 0, 32 | c_cc: [0u8; 32], 33 | c_ispeed: 0, 34 | c_ospeed: 0, 35 | }; 36 | 37 | parse_retval!(unsafe { libc::tcgetattr(fd, &mut attr) })?; 38 | 39 | Ok(attr) 40 | } 41 | 42 | #[cfg(target_env = "musl")] 43 | pub fn attributes(fd: c_int) -> Result { 44 | let mut attr = termios { 45 | c_iflag: 0, 46 | c_oflag: 0, 47 | c_cflag: 0, 48 | c_lflag: 0, 49 | c_line: 0, 50 | c_cc: [0u8; 32], 51 | __c_ispeed: 0, 52 | __c_ospeed: 0, 53 | }; 54 | 55 | parse_retval!(unsafe { libc::tcgetattr(fd, &mut attr) })?; 56 | 57 | Ok(attr) 58 | } 59 | 60 | pub fn set_attributes(fd: c_int, attr: &termios) -> Result<()> { 61 | parse_retval!(unsafe { libc::tcsetattr(fd, TCSANOW, attr) }).map_err(|e| { 62 | if e.kind() == io::ErrorKind::InvalidInput { 63 | Error::InvalidValue 64 | } else { 65 | Error::Io(e) 66 | } 67 | })?; 68 | 69 | Ok(()) 70 | } 71 | 72 | pub fn line_speed(fd: c_int) -> Result { 73 | // We can't use termios2 here, because it's not supported by musl 74 | Ok(match unsafe { libc::cfgetospeed(&attributes(fd)?) } { 75 | B0 => 0, 76 | B50 => 50, 77 | B75 => 75, 78 | B110 => 110, 79 | B134 => 134, 80 | B150 => 150, 81 | B200 => 200, 82 | B300 => 300, 83 | B600 => 600, 84 | B1200 => 1_200, 85 | B1800 => 1_800, 86 | B2400 => 2_400, 87 | B4800 => 4_800, 88 | B9600 => 9_600, 89 | B19200 => 19_200, 90 | B38400 => 38_400, 91 | B57600 => 57_600, 92 | B115200 => 115_200, 93 | B230400 => 230_400, 94 | B460800 => 460_800, 95 | B500000 => 500_000, 96 | B576000 => 576_000, 97 | B921600 => 921_600, 98 | B1000000 => 1_000_000, 99 | B1152000 => 1_152_000, 100 | B1500000 => 1_500_000, 101 | B2000000 => 2_000_000, 102 | B2500000 => 2_500_000, 103 | B3000000 => 3_000_000, 104 | B3500000 => 3_500_000, 105 | B4000000 => 4_000_000, 106 | _ => return Err(Error::InvalidValue), 107 | }) 108 | } 109 | 110 | pub fn set_line_speed(fd: c_int, line_speed: u32) -> Result<()> { 111 | let baud = match line_speed { 112 | 0 => B0, 113 | 50 => B50, 114 | 75 => B75, 115 | 110 => B110, 116 | 134 => B134, 117 | 150 => B150, 118 | 200 => B200, 119 | 300 => B300, 120 | 600 => B600, 121 | 1_200 => B1200, 122 | 1_800 => B1800, 123 | 2_400 => B2400, 124 | 4_800 => B4800, 125 | 9_600 => B9600, 126 | 19_200 => B19200, 127 | 38_400 => B38400, 128 | 57_600 => B57600, 129 | 115_200 => B115200, 130 | 230_400 => B230400, 131 | 460_800 => B460800, 132 | 500_000 => B500000, 133 | 576_000 => B576000, 134 | 921_600 => B921600, 135 | 1_000_000 => B1000000, 136 | 1_152_000 => B1152000, 137 | 1_500_000 => B1500000, 138 | 2_000_000 => B2000000, 139 | 2_500_000 => B2500000, 140 | 3_000_000 => B3000000, 141 | 3_500_000 => B3500000, 142 | 4_000_000 => B4000000, 143 | _ => return Err(Error::InvalidValue), 144 | }; 145 | 146 | // We can't use termios2 here, because it's not supported by musl 147 | let mut attr = attributes(fd)?; 148 | 149 | parse_retval!(unsafe { libc::cfsetispeed(&mut attr, baud) })?; 150 | parse_retval!(unsafe { libc::cfsetospeed(&mut attr, baud) })?; 151 | 152 | set_attributes(fd, &attr) 153 | } 154 | 155 | pub fn parity(fd: c_int) -> Result { 156 | let attr = attributes(fd)?; 157 | 158 | if (attr.c_cflag & PARENB) == 0 { 159 | return Ok(Parity::None); 160 | } else if (attr.c_cflag & PARENB) > 0 && (attr.c_cflag & PARODD) == 0 { 161 | return Ok(Parity::Even); 162 | } else if (attr.c_cflag & PARENB) > 0 && (attr.c_cflag & PARODD) > 0 { 163 | return Ok(Parity::Odd); 164 | } else if (attr.c_cflag & PARENB) > 0 165 | && (attr.c_cflag & CMSPAR) > 0 166 | && (attr.c_cflag & PARODD) > 0 167 | { 168 | return Ok(Parity::Mark); 169 | } else if (attr.c_cflag & PARENB) > 0 170 | && (attr.c_cflag & CMSPAR) > 0 171 | && (attr.c_cflag & PARODD) == 0 172 | { 173 | return Ok(Parity::Space); 174 | } 175 | 176 | Ok(Parity::None) 177 | } 178 | 179 | pub fn set_parity(fd: c_int, parity: Parity) -> Result<()> { 180 | let mut attr = attributes(fd)?; 181 | 182 | match parity { 183 | Parity::None => { 184 | attr.c_cflag &= !PARENB; 185 | attr.c_cflag &= !PARODD; 186 | } 187 | Parity::Even => { 188 | attr.c_cflag |= PARENB; 189 | attr.c_cflag &= !PARODD; 190 | } 191 | Parity::Odd => { 192 | attr.c_cflag |= PARENB | PARODD; 193 | } 194 | Parity::Mark => { 195 | attr.c_cflag |= PARENB | PARODD | CMSPAR; 196 | } 197 | Parity::Space => { 198 | attr.c_cflag |= PARENB | CMSPAR; 199 | attr.c_cflag &= !PARODD; 200 | } 201 | } 202 | 203 | set_attributes(fd, &attr) 204 | } 205 | 206 | pub fn parity_check(fd: c_int) -> Result { 207 | let attr = attributes(fd)?; 208 | 209 | if (attr.c_iflag & INPCK) == 0 { 210 | return Ok(ParityCheck::None); 211 | } else if (attr.c_iflag & INPCK) > 0 212 | && (attr.c_iflag & IGNPAR) > 0 213 | && (attr.c_iflag & PARMRK) == 0 214 | { 215 | return Ok(ParityCheck::Strip); 216 | } else if (attr.c_iflag & INPCK) > 0 217 | && (attr.c_iflag & IGNPAR) == 0 218 | && (attr.c_iflag & PARMRK) == 0 219 | { 220 | return Ok(ParityCheck::Replace); 221 | } else if (attr.c_iflag & INPCK) > 0 222 | && (attr.c_iflag & IGNPAR) == 0 223 | && (attr.c_iflag & PARMRK) > 0 224 | { 225 | return Ok(ParityCheck::Mark); 226 | } 227 | 228 | Ok(ParityCheck::None) 229 | } 230 | 231 | pub fn set_parity_check(fd: c_int, parity_check: ParityCheck) -> Result<()> { 232 | let mut attr = attributes(fd)?; 233 | 234 | match parity_check { 235 | ParityCheck::None => attr.c_iflag &= !(INPCK | IGNPAR | PARMRK), 236 | ParityCheck::Strip => { 237 | attr.c_iflag |= INPCK | IGNPAR; 238 | attr.c_iflag &= !PARMRK; 239 | } 240 | ParityCheck::Replace => { 241 | attr.c_iflag |= INPCK; 242 | attr.c_iflag &= !(IGNPAR | PARMRK); 243 | } 244 | ParityCheck::Mark => { 245 | attr.c_iflag |= INPCK | PARMRK; 246 | attr.c_iflag &= !IGNPAR; 247 | } 248 | } 249 | 250 | set_attributes(fd, &attr) 251 | } 252 | 253 | pub fn data_bits(fd: c_int) -> Result { 254 | let attr = attributes(fd)?; 255 | 256 | Ok(match attr.c_cflag & CSIZE { 257 | CS5 => 5, 258 | CS6 => 6, 259 | CS7 => 7, 260 | CS8 => 8, 261 | _ => return Err(Error::InvalidValue), 262 | }) 263 | } 264 | 265 | pub fn set_data_bits(fd: c_int, data_bits: u8) -> Result<()> { 266 | let mut attr = attributes(fd)?; 267 | 268 | attr.c_cflag &= !CSIZE; 269 | match data_bits { 270 | 5 => attr.c_cflag |= CS5, 271 | 6 => attr.c_cflag |= CS6, 272 | 7 => attr.c_cflag |= CS7, 273 | 8 => attr.c_cflag |= CS8, 274 | _ => return Err(Error::InvalidValue), 275 | } 276 | 277 | set_attributes(fd, &attr) 278 | } 279 | 280 | pub fn stop_bits(fd: c_int) -> Result { 281 | let attr = attributes(fd)?; 282 | 283 | if (attr.c_cflag & CSTOPB) > 0 { 284 | Ok(2) 285 | } else { 286 | Ok(1) 287 | } 288 | } 289 | 290 | pub fn set_stop_bits(fd: c_int, stop_bits: u8) -> Result<()> { 291 | let mut attr = attributes(fd)?; 292 | 293 | match stop_bits { 294 | 1 => attr.c_cflag &= !CSTOPB, 295 | 2 => attr.c_cflag |= CSTOPB, 296 | _ => return Err(Error::InvalidValue), 297 | } 298 | 299 | set_attributes(fd, &attr) 300 | } 301 | 302 | // Enable non-canonical mode 303 | pub fn set_raw_mode(fd: c_int) -> Result<()> { 304 | let mut attr = attributes(fd)?; 305 | 306 | // Change flags to enable non-canonical mode 307 | unsafe { 308 | libc::cfmakeraw(&mut attr); 309 | } 310 | 311 | set_attributes(fd, &attr) 312 | } 313 | 314 | // Return minimum input queue length and timeout duration for read() 315 | pub fn read_mode(fd: c_int) -> Result<(usize, Duration)> { 316 | let attr = attributes(fd)?; 317 | 318 | Ok(( 319 | attr.c_cc[VMIN] as usize, 320 | // Converted from deciseconds 321 | Duration::from_millis(u64::from(attr.c_cc[VTIME]) * 100), 322 | )) 323 | } 324 | 325 | // Set minimum input queue length and timeout duration for read() 326 | pub fn set_read_mode(fd: c_int, min_length: u8, timeout: Duration) -> Result<()> { 327 | let mut attr = attributes(fd)?; 328 | 329 | attr.c_cc[VMIN] = min_length; 330 | // Specified in deciseconds 331 | attr.c_cc[VTIME] = (timeout.as_secs() * 10) 332 | .saturating_add(u64::from(timeout.subsec_micros() / 100_000)) 333 | .min(255) as u8; 334 | 335 | set_attributes(fd, &attr) 336 | } 337 | 338 | // If CREAD isn't set, all input is discarded 339 | pub fn enable_read(fd: c_int) -> Result<()> { 340 | let mut attr = attributes(fd)?; 341 | attr.c_cflag |= CREAD; 342 | 343 | set_attributes(fd, &attr) 344 | } 345 | 346 | // Ignore carrier detect signal 347 | pub fn ignore_carrier_detect(fd: c_int) -> Result<()> { 348 | let mut attr = attributes(fd)?; 349 | attr.c_cflag |= CLOCAL; 350 | 351 | set_attributes(fd, &attr) 352 | } 353 | 354 | // Return RTS/CTS flow control setting 355 | pub fn hardware_flow_control(fd: c_int) -> Result { 356 | let attr = attributes(fd)?; 357 | 358 | Ok((attr.c_cflag & CRTSCTS) > 0) 359 | } 360 | 361 | // Set RTS/CTS flow control 362 | pub fn set_hardware_flow_control(fd: c_int, enabled: bool) -> Result<()> { 363 | let mut attr = attributes(fd)?; 364 | 365 | if enabled { 366 | attr.c_cflag |= CRTSCTS; 367 | } else { 368 | attr.c_cflag &= !CRTSCTS; 369 | } 370 | 371 | set_attributes(fd, &attr) 372 | } 373 | 374 | // Return control signal status 375 | pub fn status(fd: c_int) -> Result { 376 | let mut tiocm: c_int = 0; 377 | 378 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 379 | 380 | Ok(tiocm) 381 | } 382 | 383 | // Return CTS state 384 | pub fn cts(fd: c_int) -> Result { 385 | let mut tiocm: c_int = 0; 386 | 387 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 388 | 389 | Ok(tiocm & TIOCM_CTS > 0) 390 | } 391 | 392 | // Return RTS state 393 | pub fn rts(fd: c_int) -> Result { 394 | let mut tiocm: c_int = 0; 395 | 396 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 397 | 398 | Ok(tiocm & TIOCM_RTS > 0) 399 | } 400 | 401 | // Assert / release RTS line 402 | pub fn set_rts(fd: c_int, rts: bool) -> Result<()> { 403 | let tiocm: c_int = TIOCM_RTS; 404 | 405 | parse_retval!(unsafe { libc::ioctl(fd, if rts { TIOCMBIS } else { TIOCMBIC }, &tiocm) })?; 406 | 407 | Ok(()) 408 | } 409 | 410 | // Return DCD state 411 | pub fn dcd(fd: c_int) -> Result { 412 | let mut tiocm: c_int = 0; 413 | 414 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 415 | 416 | Ok(tiocm & TIOCM_CAR > 0) 417 | } 418 | 419 | // Return RI state 420 | pub fn ri(fd: c_int) -> Result { 421 | let mut tiocm: c_int = 0; 422 | 423 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 424 | 425 | Ok(tiocm & TIOCM_RNG > 0) 426 | } 427 | 428 | // Return DSR state 429 | pub fn dsr(fd: c_int) -> Result { 430 | let mut tiocm: c_int = 0; 431 | 432 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 433 | 434 | Ok(tiocm & TIOCM_DSR > 0) 435 | } 436 | 437 | // Return DTR state 438 | pub fn dtr(fd: c_int) -> Result { 439 | let mut tiocm: c_int = 0; 440 | 441 | parse_retval!(unsafe { libc::ioctl(fd, TIOCMGET, &mut tiocm) })?; 442 | 443 | Ok(tiocm & TIOCM_DTR > 0) 444 | } 445 | 446 | // Assert / release DTR line 447 | pub fn set_dtr(fd: c_int, dtr: bool) -> Result<()> { 448 | let tiocm: c_int = TIOCM_DTR; 449 | 450 | parse_retval!(unsafe { libc::ioctl(fd, if dtr { TIOCMBIS } else { TIOCMBIC }, &tiocm) })?; 451 | 452 | Ok(()) 453 | } 454 | 455 | // Return XON/XOFF flow control setting 456 | pub fn software_flow_control(fd: c_int) -> Result<(bool, bool)> { 457 | let attr = attributes(fd)?; 458 | 459 | Ok(((attr.c_iflag & IXOFF) > 0, (attr.c_iflag & IXON) > 0)) 460 | } 461 | 462 | // Set XON/XOFF flow control 463 | pub fn set_software_flow_control( 464 | fd: c_int, 465 | incoming_enabled: bool, 466 | outgoing_enabled: bool, 467 | ) -> Result<()> { 468 | let mut attr = attributes(fd)?; 469 | 470 | attr.c_iflag &= !(IXON | IXOFF | IXANY); 471 | attr.c_cc[VSTART] = XON; 472 | attr.c_cc[VSTOP] = XOFF; 473 | 474 | if incoming_enabled { 475 | attr.c_iflag |= IXOFF; 476 | } 477 | 478 | if outgoing_enabled { 479 | attr.c_iflag |= IXON; 480 | } 481 | 482 | set_attributes(fd, &attr) 483 | } 484 | 485 | // Send XOFF 486 | pub fn send_stop(fd: c_int) -> Result<()> { 487 | parse_retval!(unsafe { libc::tcflow(fd, TCIOFF) })?; 488 | 489 | Ok(()) 490 | } 491 | 492 | // Send XON 493 | pub fn send_start(fd: c_int) -> Result<()> { 494 | parse_retval!(unsafe { libc::tcflow(fd, TCION) })?; 495 | 496 | Ok(()) 497 | } 498 | 499 | // Discard all waiting incoming and outgoing data 500 | pub fn flush(fd: c_int, queue_type: Queue) -> Result<()> { 501 | parse_retval!(unsafe { 502 | libc::tcflush( 503 | fd, 504 | match queue_type { 505 | Queue::Input => TCIFLUSH, 506 | Queue::Output => TCOFLUSH, 507 | Queue::Both => TCIOFLUSH, 508 | }, 509 | ) 510 | })?; 511 | 512 | Ok(()) 513 | } 514 | 515 | // Wait until all outgoing data has been transmitted 516 | pub fn drain(fd: c_int) -> Result<()> { 517 | parse_retval!(unsafe { libc::tcdrain(fd) })?; 518 | 519 | Ok(()) 520 | } 521 | 522 | // Returns the number of bytes waiting in the input queue. 523 | pub fn input_len(fd: c_int) -> Result { 524 | let mut len: c_int = 0; 525 | 526 | parse_retval!(unsafe { libc::ioctl(fd, TIOCINQ, &mut len) })?; 527 | 528 | Ok(len as usize) 529 | } 530 | 531 | // Returns the number of bytes waiting in the output queue. 532 | pub fn output_len(fd: c_int) -> Result { 533 | let mut len: c_int = 0; 534 | 535 | parse_retval!(unsafe { libc::ioctl(fd, TIOCOUTQ, &mut len) })?; 536 | 537 | Ok(len as usize) 538 | } 539 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.22.1 (December 10, 2024) 4 | 5 | * Update example documentation to be compatible with both older and newer Pi models. 6 | 7 | ## 0.22.0 (December 10, 2024) 8 | 9 | * **Pwm**: (Breaking change) Add support for `Pwm2` and `Pwm3`. 10 | * **Pwm**: On Raspberry Pi 5 and later models, the PWM channel mapping has changed to be consistent with sysfs numbering. `Pwm0` = GPIO12, `Pwm1` = GPIO13, `Pwm2` = GPIO18, `Pwm3` = GPIO19. 11 | * **Pwm**: Add `with_pwmchip` to address PWM channels outside of the default configuration. 12 | 13 | ## 0.21.0 (December 10, 2024) 14 | 15 | * Add support for Raspberry Pi Compute Module 5 Lite. 16 | * Add support for Raspberry Pi 500. 17 | * **Pwm**: (Breaking change) Add `InvalidChannel` error. 18 | * **Pwm**: Add `TryFrom` implementation for `Channel`. 19 | 20 | ## 0.20.0 (November 30, 2024) 21 | 22 | * Add support for Raspberry Pi Compute Module 5. 23 | * **Gpio**: Add `set_bias` to `InputPin` (contributed by @KronsyC). 24 | * **Gpio**: Add shared state button example (contributed by @CosminPerRam). 25 | * **Spi**: Fix embedded HAL `SimpleHalSpiDevice` transactions to keep CS low between operations (contributed by @whatisbyandby). 26 | * **Spi**: (Breaking change) Change `SimpleHalSpiDevice::new()` to require an `Spi` instance, instead of a generic HAL bus (contributed by @whatisbyandby). 27 | * **Uart**: Check if `/dev/ttyAMA0` exists and fall back to `/dev/serial0` when calling `new()` on Raspberry Pi 5 or Compute Module 5. 28 | 29 | ## 0.19.0 (Aug 14, 2024) 30 | 31 | * **Gpio**: Add `Event` struct, containing interrupt event details. 32 | * **Gpio**: (Breaking change) Add optional `debounce` argument to `set_interrupt()` and `set_async_interrupt()`. 33 | * **Gpio**: (Breaking change) Return an `Event` struct when an interrupt is triggered, instead of `Level`. 34 | 35 | ## 0.18.0 (May 18, 2024) 36 | 37 | * **Gpio**: (Breaking change) Add support for mode `Null` for uninitialized (FUNCSEL 31 (NULL)) pins on Raspberry Pi 5 (contributed by @lukeburong). 38 | * **Gpio**: Fix `InputPin` mode not getting set correctly for uninitialized pins on Raspberry Pi 5 (contributed by @lukeburong). 39 | * **Pwm**: (Breaking change) Add `Error::UnknownModel`. 40 | * **Pwm**: Fix support for hardware PWM on Raspberry Pi 5, caused by incorrect PWM chip/channel. 41 | 42 | ## 0.17.1 (January 21, 2024) 43 | 44 | * Fix `is_low` implementation for `embedded_hal::digital::InputPin` (contributed by @TeyKey1). 45 | 46 | ## 0.17.0 (January 15, 2024) 47 | 48 | * (Breaking change) Update `embedded-hal` to v1.0.0 (contributed by @reitermarkus). 49 | 50 | ## 0.16.1 (December 20, 2023) 51 | 52 | * Minor documentation changes. 53 | 54 | ## 0.16.0 (December 10, 2023) 55 | 56 | * (Breaking change) Update `embedded-hal` to v1.0.0-rc.2 (contributed by @reitermarkus). 57 | * (Breaking change) Update `embedded-hal` to v1.0.0-rc.1 (contributed by @mbuesch). 58 | * (Breaking change) Change Minimum Supported Rust Version (MSRV) to v1.60.0. 59 | * Remove `Hardware` field check in `/proc/cpuinfo`. 60 | * Fix device recognition for Raspberry Pi Zero 2 W (2021) on Arch Linux (contributed by @denisandroid). 61 | 62 | ## 0.15.0 (October 18, 2023) 63 | 64 | * Add support for Raspberry Pi 5 (thanks @ukscone for all the testing!). 65 | * **Gpio**: (Breaking change) Rename `PullUpDown` enum to `Bias`, and `set_pullupdown()` to `set_bias()`. 66 | * **Gpio**: Add support for new modes `Alt6`, `Alt7` and `Alt8`. 67 | * **I2c**: Implement I2C transactions for `embedded-hal` (contributed by @CBJamo). 68 | 69 | ## 0.14.1 (November 25, 2022) 70 | 71 | * **Gpio**: Fix subtract underflow panic in software-based PWM. 72 | 73 | ## 0.14.0 (November 9, 2022) 74 | 75 | * (Breaking change) Transition to Rust 2021, requiring rustc v1.56.0 or newer to compile the library. 76 | * **Gpio**: Implement `unproven` `embedded-hal` trait `digital::v2::IoPin` for `IoPin` (contributed by @rumatoest). 77 | * **Gpio**: Implement `From` trait for `Level` (contributed by @makerio90). 78 | * **Gpio**: Fix error when configuring an InputPin for GPIOs > 31 on BCM2711 (contributed by @benkard). 79 | * **Gpio**: Fix access to GPIO54 - GPIO57 on BCM2711. 80 | * **Gpio**: (Breaking change) Add `Error::PinUsed`, returned by `Gpio::get()` to indicate a pin is already in use. 81 | * **Gpio**: (Breaking change) Change `Error::PinNotAvailable`, returned by `Gpio::get()` to indicate a pin isn't available on the current Raspberry Pi model. 82 | * Update `embedded-hal` to v1.0.0-alpha.9 (contributed by @mbuesch). 83 | 84 | ## 0.13.1 (October 28, 2021) 85 | 86 | * **DeviceInfo**: Add device identification support for Raspberry Pi Compute Module 4 models with 4GB and 8GB RAM. 87 | * **DeviceInfo**: Add device identification support for Raspberry Pi Zero 2 W. 88 | 89 | ## 0.13.0 (September 27, 2021) 90 | 91 | * Add support for `embedded-hal` v1.0.0-alpha.5 (contributed by @reitermarkus). 92 | * **Gpio**: Add `into_output_low()` and `into_output_high()` to `Pin` to set the logic level before changing the pin mode. 93 | * **Gpio**: Implement `From` trait for `Level`, where `0` is converted into `Level::Low`, and any other value into `Level::High`. 94 | 95 | ## 0.12.0 (April 17, 2021) 96 | 97 | * (Breaking change) Require rustc v1.45.0 or newer to compile the library due to std API stabilizations. 98 | * **DeviceInfo**: (Breaking change) Replace manual non-exhaustive pattern implementations for `Model` and `SoC` with `#[non_exhaustive]`. 99 | * **DeviceInfo**: Add device identification support for Raspberry Pi 4 B v1.2, Raspberry Pi 400 and Raspberry Pi Compute Module 4. 100 | * **Gpio**: Fix built-in pull-up/pull-down resistor support for Raspberry Pi 4 B (contributed by @Dragonrun1). 101 | * **Gpio**: Add BCM2711 ioctl support (contributed by @foxzool). 102 | * **Hal**: (Breaking change) Upgrade `embedded-hal` trait implementations for `digital::v1::{InputPin, OutputPin, StatefulOutputPin, ToggleableOutputPin}` to `digital::v2`. These can be explicitly converted back to `digital::v1` through `digital::v1_compat::{OldInputPin, OldOutputPin}` for backwards compatibility with older drivers. 103 | * **I2c**: Accept slave addresses below 0x08 (contributed by @Majkl578). 104 | * **I2c**: Add documentation for I2C3, I2C4, I2C5 and I2C6. 105 | * **Pwm**: Improve error messages (contributed by @binarybana). 106 | * **Pwm**: Improve documentation for Ubuntu (contributed by @binarybana). 107 | * **Spi**: (Breaking change) Add support for SPI3, SPI4, SPI5 and SPI6. 108 | 109 | ## 0.11.3 (June 24, 2019) 110 | 111 | * **DeviceInfo**: Add device identification support for Raspberry Pi 4 B. (Support for the new peripherals will be added in 0.12.0) 112 | 113 | ## 0.11.2 (May 2, 2019) 114 | 115 | * Add `hal-unproven` feature flag (disabled by default), which enables `unproven` `embedded-hal` trait implementations. Note that `embedded-hal`'s `unproven` traits don't follow semver rules. Patch releases may introduce breaking changes. 116 | * **Gpio**: Implement `Sync` trait for `IoPin` and `OutputPin`. 117 | * **Gpio**: Implement `unproven` `embedded-hal` trait `digital::InputPin` for `Pin`, `InputPin`, `OutputPin` and `IoPin`. 118 | * **Gpio**: Implement `unproven` `embedded-hal` traits `digital::{StatefulOutputPin, ToggleableOutputPin}` and `Pwm` for `OutputPin` and `IoPin`. 119 | * **Gpio**: Remove internal `MSG_WAITING` flag from software PWM implementation to resolve an issue found in the wild causing delays in message processing (contributed by @aehmlo). 120 | * **Hal**: Add `hal` module, containing `embedded-hal` trait implementations that aren't tied to a specific peripheral. 121 | * **Hal**: Implement `embedded-hal` traits `blocking::delay::{DelayMs, DelayUs}` for `Delay`. 122 | * **Hal**: Implement `embedded-hal` trait `timer::CountDown` for `Timer` (contributed by @jacobrosenthal). 123 | * **Pwm**: Implement `Display` trait for `Channel` and `Polarity`. 124 | * **Pwm**: Implement `unproven` `embedded-hal` trait `Pwm` for `Pwm`. 125 | * **Spi**: Implement `Display` trait for `BitOrder`, `Bus`, `Mode`, `Polarity` and `SlaveSelect`. 126 | * **Spi**: Remove `From` implementation due to a conflict with `nb` v0.1.2 (contributed by @gferon). 127 | * **Uart**: Add support for the PL011 and mini UART peripherals, USB to serial adapters, XON/XOFF software flow control and RTS/CTS hardware flow control. 128 | * **Uart**: Implement `embedded-hal` traits `serial::{Read, Write}` and `blocking::serial::Write` for `Uart`. 129 | 130 | ## 0.11.1 (February 24, 2019) 131 | 132 | * Fix incorrect data type conversion on 64-bit OSes when libc uses 64-bit `timespec` fields. 133 | 134 | ## 0.11.0 (February 20, 2019) 135 | 136 | * Add `hal` feature flag (disabled by default), which includes `embedded-hal` trait implementations for all supported peripherals. 137 | * Add `Gpio` example demonstrating software-based PWM. 138 | * **DeviceInfo**: (Breaking change) Add support for Raspberry Pi Compute Module 3+. 139 | * **DeviceInfo**: (Breaking change) Add hidden `Model::__Nonexhaustive` and `SoC::__Nonexhaustive` variants, indicating `Model` and `SoC` shouldn't be exhaustively matched. After this change, adding new variants to these enums when a new Raspberry Pi model is released won't be considered a breaking change anymore. This is a hack that can still be circumvented, but anyone that does so should be aware of the repercussions. This will be replaced once `#[non_exhaustive]` stabilizes. 140 | * **Gpio**: Add software-based PWM to `OutputPin` and `IoPin` through `set_pwm()`, `set_pwm_frequency()` and `clear_pwm()`. 141 | * **Gpio**: Add `is_set_low()` and `is_set_high()` to `OutputPin` to check the pin's output state. 142 | * **Gpio**: Implement `embedded-hal` traits `digital::OutputPin` and `PwmPin` for `OutputPin` and `IoPin`. 143 | * **I2c**: Implement `embedded-hal` traits `blocking::i2c::{Read, Write, WriteRead}` for `I2c`. 144 | * **Pwm**: Add `reset_on_drop()` and `set_reset_on_drop()` to `Pwm` to optionally keep the PWM channel enabled on drop (contributed by @benkard). 145 | * **Pwm**: Implement `embedded-hal` trait `PwmPin` for `Pwm`. 146 | * **Spi**: Implement `embedded-hal` traits `spi::FullDuplex` and `blocking::spi::{Transfer, Write}` for `Spi`. 147 | 148 | ## 0.10.0 (January 18, 2019) 149 | 150 | * (Breaking change) Transition to Rust 2018, requiring rustc v1.31.0 or newer to compile the library. 151 | * Add new badge to `README.md`, indicating the required minimum rustc version. 152 | * Add `Gpio`, `I2c`, `Pwm` and `Spi` examples to the examples subdirectory. 153 | * Rewrite `Display` formatting for `Error`s in all modules to include more details when available. 154 | * **DeviceInfo**: (Breaking change) Remove `DeviceInfo::peripheral_base()` and `DeviceInfo::gpio_offset()` from the public API. 155 | * **Gpio**: (Breaking change) Move pin-specific methods from `Gpio` to the new `InputPin`/`OutputPin` structs. Access pins through `Gpio::get()` (contributed by @reitermarkus). 156 | * **Gpio**: Add a new `IoPin` struct which allows mode switching between input, output or an alternate function. 157 | * **Gpio**: `Gpio::get()` returns an owned unconfigured `Pin`, which can be used to read the pin's mode and logic level. Convert a `Pin` to an `InputPin`, `OutputPin` or `IoPin` through the various `Pin::into_` methods to access additional functionality. 158 | * **Gpio**: Add a variety of convenience methods to `InputPin`, `OutputPin` and `IoPin` for common tasks. 159 | * **Gpio**: (Breaking change) Remove `Error::NotInitialized`, `Error::UnknownMode` and `Error::InvalidPin` (contributed by @reitermarkus). 160 | * **Gpio**: (Breaking change) Remove `Error::InstanceExists`. Multiple (thread-safe) `Gpio` instances can now exist simultaneously. 161 | * **Gpio**: (Breaking change) Rename `Error::UnknownSoC` to `Error::UnknownModel` for consistency. 162 | * **Gpio**: (Breaking change) Add relevant file path to `Error::PermissionDenied` to make it easier to solve file permission issues. 163 | * **Gpio**: (Breaking change) Add `Error::PinNotAvailable`, returned by `Gpio::get()` to indicate a pin is already in use, or isn't available on the current Raspberry Pi model. 164 | * **Gpio**: (Breaking change) Rename `clear_on_drop()`/`set_clear_on_drop()` to `reset_on_drop()`/`set_reset_on_drop()` for clarity. 165 | * **Gpio**: (Breaking change) Change `Gpio::poll_interrupts()` `pins` input parameter and return type from `u8` to `&InputPin` (contributed by @reitermarkus). 166 | * **Gpio**: When a pin goes out of scope, if an asynchronous interrupt trigger was configured for the pin, the polling thread will get stopped. 167 | * **Gpio**: Disable built-in pull-up/pull-down resistors when a pin goes out of scope and `reset_on_drop` is set to true. 168 | * **Gpio**: Implement `Clone` for `Gpio`. 169 | * **I2c**: (Breaking change) Rename `Error::UnknownSoC` to `Error::UnknownModel` for consistency. 170 | * **Pwm**: (Breaking change) Rename `duty_cycle()` to `pulse_width()` and `set_duty_cycle()` to `set_pulse_width()` to better reflect the specified value type. 171 | * **Pwm**: (Breaking change) Rename `enabled()` to `is_enabled()` for more idiomatic predicates. 172 | * **Pwm**: Add `duty_cycle()`, `set_duty_cycle()` and `frequency()` convenience methods that convert between frequency/duty cycle and period/pulse width. 173 | * **Pwm**: Fix incorrect return values for `period()`, `duty_cycle()`, `polarity()` and `enabled()` caused by whitespace. 174 | * **Spi**: (Breaking change) Rename `TransferSegment` to `Segment`. 175 | * **Spi**: (Breaking change) `Segment::new()` parameters are no longer wrapped in `Option`. Use `Segment::with_read()` or `Segment::with_write()` instead when a full-duplex transfer isn't needed. 176 | * **Spi**: Add `Segment::with_read()` and `Segment::with_write()` convenience methods for read operations without any outgoing data, or write operations where any incoming data should be discarded. 177 | 178 | ## 0.9.0 (November 15, 2018) 179 | 180 | * **DeviceInfo**: (Breaking change) Add support for Raspberry Pi 3 A+. 181 | 182 | ## 0.8.1 (October 5, 2018) 183 | 184 | * Add support for musl (contributed by @gferon). 185 | 186 | ## 0.8.0 (August 14, 2018) 187 | 188 | * **Gpio**: Replace GPIO sysfs interface (`/sys/class/gpio`) for interrupts with GPIO character device (`/dev/gpiochipN`). 189 | * **Pwm**: Add support for up to two hardware PWM channels with configurable period/frequency, duty cycle and polarity. 190 | * **Spi**: Fix 0-length transfers caused by `TransferSegment` instances with `write_buffer` set to `None`. 191 | 192 | ## 0.7.1 (June 26, 2018) 193 | 194 | * Revert the use of the recently stabilized `Duration::subsec_millis()` back to `Duration::subsec_nanos()` to allow older stable versions of the compiler to work. 195 | 196 | ## 0.7.0 (June 26, 2018) 197 | 198 | * **DeviceInfo**: (Breaking change) Remove `Error::CantAccessProcCpuInfo`. 199 | * **DeviceInfo**: Add additional options to automatically identify the Pi model when /proc/cpuinfo contains inaccurate data. 200 | * **Gpio**: (Breaking change) Remove `Error::ChannelDisconnected`. 201 | * **I2c**: Add support for I2C with basic read/write, block read/write, and write_read. 202 | * **I2c**: Add support for SMBus with Quick Command, Send/Receive Byte, Read/Write Byte/Word, Process Call, Block Write, and PEC. 203 | * Reduce external dependencies. 204 | 205 | ## 0.6.0 (June 1, 2018) 206 | 207 | * **DeviceInfo**: (Breaking change) Return model and soc by value, rather than by reference. 208 | * **DeviceInfo**: (Breaking change) Remove `SoC::Bcm2837` to reduce ambiguity. The Pi 3B and Compute Module 3 now return the more accurate `SoC::Bcm2837A1`. 209 | * **DeviceInfo**: (Breaking change) Remove `SoC::Unknown`. An unknown SoC is now treated as a failure. 210 | * **DeviceInfo**: Return the actual SoC based on the Raspberry Pi model, rather than the inaccurate `/proc/cpuinfo` data. 211 | * **Gpio**: (Breaking change) Remove `InterruptError`. Merge remaining errors with `Error`. 212 | * **Gpio**: (Breaking change) Replace all `DevGpioMem` and `DevMem` errors with `Error::PermissionDenied` and `Error::Io`. 213 | * **Gpio**: (Breaking change) Change the return value for `poll_interrupt()` and `poll_interrupts()` to `Ok(Option)` on success, with `Some()` indicating an interrupt triggered, and `None` indicating a timeout occurred. 214 | * **Gpio**: (Breaking change) Only a single instance of `Gpio` can exist at any time. Creating another instance before the existing one goes out of scope will return an `Error::InstanceExists`. 215 | * **Spi**: Add support for SPI with half-duplex reads/writes and full-duplex multi-segment transfers. 216 | 217 | ## 0.5.1 (May 19, 2018) 218 | 219 | * **Gpio**: Add `poll_interrupts()`, which waits for multiple synchronous interrupts at the same time. 220 | * **Gpio**: Add public interface for `InterruptError`. 221 | * Cleanup documentation. 222 | 223 | ## 0.5.0 (May 9, 2018) 224 | 225 | * **DeviceInfo**: Add hardcoded Raspberry Pi 3 B+ SoC identifier, rather than relying on inaccurate info from `/proc/cpuinfo`. 226 | * **Gpio**: Add support for asynchronous interrupts (`set_async_interrupt()`, `clear_async_interrupt()`). 227 | * **Gpio**: Add support for synchronous interrupts (`set_interrupt()`, `clear_interrupt()`, `poll_interrupt()`). 228 | 229 | ## 0.4.0 (March 19, 2018) 230 | 231 | * **Gpio**: Replace `&mut self` with `&self` where possible. 232 | 233 | ## 0.3.0 (March 16, 2018) 234 | 235 | * **DeviceInfo**: (Breaking change) Add support for Raspberry Pi 3 B+. 236 | * **DeviceInfo**: Set memory offsets based on model info rather than SoC. 237 | 238 | ## 0.2.0 (October 6, 2017) 239 | 240 | * (Breaking change) To adhere to Rust's naming conventions, several structs and enums that had GPIO, IO, BCM or CPU in their name have been changed to `Gpio`, `Io`, `Bcm` and `Cpu` respectively. 241 | * **Gpio**: Add GPIO as a temporary (deprecated) type alias for `Gpio`. 242 | 243 | ## 0.1.3 (May 27, 2017) 244 | 245 | * **DeviceInfo**: Add additional revision codes for old models. 246 | * **GPIO**: Always try `/dev/mem` after `/dev/gpiomem` fails. Return new error `PermissionDenied` when both `/dev/gpiomem` and `/dev/mem` have permission issues. This is a workaround for Ubuntu Core 16 where `/dev/gpiomem` can't be accessed by applications installed using snap (reported by @VBota1). 247 | 248 | ## 0.1.2 (March 3, 2017) 249 | 250 | * **DeviceInfo**: Change returned `u32` references to copied values. 251 | -------------------------------------------------------------------------------- /src/pwm.rs: -------------------------------------------------------------------------------- 1 | //! Interface for the PWM peripheral. 2 | //! 3 | //! RPPAL controls the Raspberry Pi's PWM peripheral through the `pwm` sysfs 4 | //! interface. 5 | //! 6 | //! ## PWM channels 7 | //! 8 | //! ### Older models (older than Raspberry Pi 5) 9 | //! 10 | //! The BCM283x SoC supports 2 hardware PWM channels. By default, the channels are 11 | //! mapped as follows: 12 | //! 13 | //! * [Pwm0](Channel::Pwm0) = GPIO12/GPIO18 14 | //! * [Pwm1](Channel::Pwm1) = GPIO13/GPIO19 15 | //! 16 | //! Consult the official documentation on how to enable and configure these. 17 | //! 18 | //! The Raspberry Pi's analog audio output uses both PWM channels. Playing audio and 19 | //! simultaneously accessing a PWM channel may cause issues. 20 | //! 21 | //! Some of the GPIO pins capable of supporting hardware PWM can also be configured for 22 | //! use with other peripherals. Be careful not to enable two peripherals on the same pin 23 | //! at the same time. 24 | //! 25 | //! ### Newer models (Raspberry Pi 5 and later) 26 | //! 27 | //! The Raspberry Pi 5 and other recent models support 4 hardware PWM channels. By 28 | //! default, the channels are mapped as follows: 29 | //! 30 | //! * [Pwm0](Channel::Pwm0) = GPIO12 31 | //! * [Pwm1](Channel::Pwm1) = GPIO13 32 | //! * [Pwm2](Channel::Pwm2) = GPIO18 33 | //! * [Pwm3](Channel::Pwm3) = GPIO19 34 | //! 35 | //! Consult the official documentation on how to enable and configure these. 36 | //! 37 | //! Some of the GPIO pins capable of supporting hardware PWM can also be configured for 38 | //! use with other peripherals. Be careful not to enable two peripherals on the same pin 39 | //! at the same time. 40 | //! 41 | //! ## Troubleshooting 42 | //! 43 | //! ### Permission denied 44 | //! 45 | //! If [`new`] returns an `io::ErrorKind::PermissionDenied` error, make sure 46 | //! `/sys/class/pwm` and its subdirectories has the appropriate permissions for the current user. 47 | //! Alternatively, you can launch your application using `sudo`. 48 | //! 49 | //! ### Not found 50 | //! 51 | //! If [`new`] returns an `io::ErrorKind::NotFound` error, you may have 52 | //! forgotten to enable the selected PWM channel. Consult the official Raspberry Pi documentation 53 | //! to correctly configure the needed channel(s). 54 | //! 55 | //! [`new`]: struct.Pwm.html#method.new 56 | 57 | use std::error; 58 | use std::fmt; 59 | use std::io; 60 | use std::result; 61 | use std::time::Duration; 62 | 63 | #[cfg(any( 64 | feature = "embedded-hal-0", 65 | feature = "embedded-hal", 66 | feature = "embedded-hal-nb" 67 | ))] 68 | mod hal; 69 | #[cfg(feature = "hal-unproven")] 70 | mod hal_unproven; 71 | mod sysfs; 72 | 73 | use crate::system::DeviceInfo; 74 | 75 | const NANOS_PER_SEC: f64 = 1_000_000_000.0; 76 | 77 | /// Errors that can occur when accessing the PWM peripheral. 78 | #[derive(Debug)] 79 | pub enum Error { 80 | /// I/O error. 81 | Io(io::Error), 82 | /// Unknown model. 83 | /// 84 | /// The Raspberry Pi model or SoC can't be identified. Support for 85 | /// new models is usually added shortly after they are officially 86 | /// announced and available to the public. Make sure you're using 87 | /// the latest release of RPPAL. 88 | /// 89 | /// You may also encounter this error if your Linux distribution 90 | /// doesn't provide any of the common user-accessible system files 91 | /// that are used to identify the model and SoC. 92 | UnknownModel, 93 | /// Invalid channel. 94 | /// 95 | /// The selected PWM channel is not available on this device. You may 96 | /// encounter this error if the Raspberry Pi model only has a limited 97 | /// number of channels, the selected channel hasn't been properly 98 | /// configured in `/boot/firmware/config.txt`, or the channel isn't 99 | /// supported by RPPAL. 100 | InvalidChannel, 101 | } 102 | 103 | impl fmt::Display for Error { 104 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 105 | match *self { 106 | Error::Io(ref err) => write!(f, "I/O error: {}", err), 107 | Error::UnknownModel => write!(f, "Unknown Raspberry Pi model"), 108 | Error::InvalidChannel => write!(f, "Invalid PWM channel"), 109 | } 110 | } 111 | } 112 | 113 | impl error::Error for Error {} 114 | 115 | impl From for Error { 116 | fn from(err: io::Error) -> Error { 117 | Error::Io(err) 118 | } 119 | } 120 | 121 | /// Result type returned from methods that can have `pwm::Error`s. 122 | pub type Result = result::Result; 123 | 124 | /// PWM channels. 125 | /// 126 | /// More information on enabling and configuring the PWM channels can be 127 | /// found [here]. 128 | /// 129 | /// [here]: index.html 130 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 131 | pub enum Channel { 132 | Pwm0 = 0, 133 | Pwm1 = 1, 134 | Pwm2 = 2, 135 | Pwm3 = 3, 136 | } 137 | 138 | impl fmt::Display for Channel { 139 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 140 | match *self { 141 | Channel::Pwm0 => write!(f, "Pwm0"), 142 | Channel::Pwm1 => write!(f, "Pwm1"), 143 | Channel::Pwm2 => write!(f, "Pwm2"), 144 | Channel::Pwm3 => write!(f, "Pwm3"), 145 | } 146 | } 147 | } 148 | 149 | impl TryFrom for Channel { 150 | type Error = Error; 151 | 152 | fn try_from(value: u8) -> result::Result { 153 | match value { 154 | 0 => Ok(Channel::Pwm0), 155 | 1 => Ok(Channel::Pwm1), 156 | 2 => Ok(Channel::Pwm2), 157 | 3 => Ok(Channel::Pwm3), 158 | _ => Err(Error::InvalidChannel), 159 | } 160 | } 161 | } 162 | 163 | /// Output polarities. 164 | #[derive(Debug, PartialEq, Eq, Copy, Clone)] 165 | pub enum Polarity { 166 | Normal, 167 | Inverse, 168 | } 169 | 170 | impl fmt::Display for Polarity { 171 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 172 | match *self { 173 | Polarity::Normal => write!(f, "Normal"), 174 | Polarity::Inverse => write!(f, "Inverse"), 175 | } 176 | } 177 | } 178 | 179 | /// Provides access to the Raspberry Pi's PWM peripheral. 180 | /// 181 | /// Before using `Pwm`, make sure the selected PWM channel has been configured 182 | /// and activated. More information can be found [here]. 183 | /// 184 | /// The `embedded-hal` trait implementations for `Pwm` can be enabled by specifying 185 | /// the optional `hal` feature in the dependency declaration for the `rppal` crate. 186 | /// 187 | /// [here]: index.html 188 | #[derive(Debug)] 189 | pub struct Pwm { 190 | chip: u8, 191 | channel: u8, 192 | reset_on_drop: bool, 193 | } 194 | 195 | impl Pwm { 196 | /// Constructs a new `Pwm`. 197 | /// 198 | /// `new` attempts to select the correct pwmchip device and channel based 199 | /// on the Raspberry Pi model. Use `with_pwmchip` for non-standard configurations. 200 | /// 201 | /// `new` doesn't change the channel's period, pulse width or polarity. The channel 202 | /// will remain disabled until [`enable`] is called. 203 | /// 204 | /// [`enable`]: #method.enable 205 | pub fn new(channel: Channel) -> Result { 206 | // Select chip/channel based on Pi model 207 | let device_info = DeviceInfo::new().map_err(|_| Error::UnknownModel)?; 208 | 209 | let pwmchip = device_info.pwm_chip(); 210 | let index = channel as u8; 211 | 212 | Self::with_pwmchip(pwmchip, index) 213 | } 214 | 215 | /// Constructs a new `Pwm` using the specified pwmchip and channel index. 216 | /// 217 | /// Use this method to address PWM channels with non-standard configurations on 218 | /// different pwmchip devices, or that fall outside the standard PWM channel range. 219 | /// 220 | /// `with_pwmchip` doesn't change the channel's period, pulse width or polarity. The channel 221 | /// will remain disabled until [`enable`] is called. 222 | /// 223 | /// [`enable`]: #method.enable 224 | pub fn with_pwmchip(pwmchip: u8, index: u8) -> Result { 225 | sysfs::export(pwmchip, index)?; 226 | 227 | let pwm = Pwm { 228 | chip: pwmchip, 229 | channel: index, 230 | reset_on_drop: true, 231 | }; 232 | 233 | // Always reset "enable" to 0. The sysfs interface has a bug where a previous 234 | // export may have left "enable" as 1 after unexporting. On the next export, 235 | // "enable" is still set to 1, even though the channel isn't enabled. 236 | let _ = pwm.disable(); 237 | 238 | Ok(pwm) 239 | } 240 | 241 | /// Constructs a new `Pwm` using the specified settings. 242 | /// 243 | /// `period` indicates the time it takes for the PWM channel to complete one cycle. 244 | /// 245 | /// `pulse_width` indicates the amount of time the PWM channel is active during a 246 | /// single period. 247 | /// 248 | /// `polarity` configures the active logic level as either high ([`Normal`]) 249 | /// or low ([`Inverse`]). 250 | /// 251 | /// `enabled` enables PWM on the selected channel. If `enabled` is set to `false`, 252 | /// the channel will remain disabled until [`enable`] is called. 253 | /// 254 | /// This method will fail if `period` is shorter than `pulse_width`. 255 | /// 256 | /// [`Normal`]: enum.Polarity.html#variant.Normal 257 | /// [`Inverse`]: enum.Polarity.html#variant.Inverse 258 | /// [`enable`]: #method.enable 259 | pub fn with_period( 260 | channel: Channel, 261 | period: Duration, 262 | pulse_width: Duration, 263 | polarity: Polarity, 264 | enabled: bool, 265 | ) -> Result { 266 | let pwm = Pwm::new(channel)?; 267 | 268 | // Set pulse width to 0 first in case the new period is shorter than the current pulse width 269 | let _ = sysfs::set_pulse_width(pwm.chip, pwm.channel, 0); 270 | 271 | pwm.set_period(period)?; 272 | pwm.set_pulse_width(pulse_width)?; 273 | pwm.set_polarity(polarity)?; 274 | if enabled { 275 | pwm.enable()?; 276 | } 277 | 278 | Ok(pwm) 279 | } 280 | 281 | /// Constructs a new `Pwm` using the specified settings. 282 | /// 283 | /// `with_frequency` is a convenience method that converts `frequency` to a period, 284 | /// and calculates the duty cycle as a percentage of the frequency. 285 | /// 286 | /// `frequency` is specified in hertz (Hz). 287 | /// 288 | /// `duty_cycle` is specified as a floating point value between `0.0` (0%) and `1.0` (100%). 289 | /// 290 | /// `polarity` configures the active logic level as either high ([`Normal`]) 291 | /// or low ([`Inverse`]). 292 | /// 293 | /// `enabled` enables PWM on the selected channel. If `enabled` is set to `false`, 294 | /// the channel will remain disabled until [`enable`] is called. 295 | /// 296 | /// [`Normal`]: enum.Polarity.html#variant.Normal 297 | /// [`Inverse`]: enum.Polarity.html#variant.Inverse 298 | /// [`enable`]: #method.enable 299 | pub fn with_frequency( 300 | channel: Channel, 301 | frequency: f64, 302 | duty_cycle: f64, 303 | polarity: Polarity, 304 | enabled: bool, 305 | ) -> Result { 306 | let pwm = Pwm::new(channel)?; 307 | 308 | // Set pulse width to 0 first in case the new period is shorter than the current pulse width 309 | let _ = sysfs::set_pulse_width(pwm.chip, pwm.channel, 0); 310 | 311 | // Convert to nanoseconds 312 | let period = if frequency == 0.0 { 313 | 0.0 314 | } else { 315 | (1.0 / frequency) * NANOS_PER_SEC 316 | }; 317 | let pulse_width = period * duty_cycle.clamp(0.0, 1.0); 318 | 319 | sysfs::set_period(pwm.chip, pwm.channel, period as u64)?; 320 | sysfs::set_pulse_width(pwm.chip, pwm.channel, pulse_width as u64)?; 321 | pwm.set_polarity(polarity)?; 322 | if enabled { 323 | pwm.enable()?; 324 | } 325 | 326 | Ok(pwm) 327 | } 328 | 329 | /// Returns the period. 330 | pub fn period(&self) -> Result { 331 | Ok(Duration::from_nanos(sysfs::period( 332 | self.chip, 333 | self.channel, 334 | )?)) 335 | } 336 | 337 | /// Sets the period. 338 | /// 339 | /// `period` indicates the time it takes for the PWM channel to complete one cycle. 340 | /// 341 | /// This method will fail if `period` is shorter than the current pulse width. 342 | pub fn set_period(&self, period: Duration) -> Result<()> { 343 | sysfs::set_period( 344 | self.chip, 345 | self.channel, 346 | u64::from(period.subsec_nanos()) 347 | .saturating_add(period.as_secs().saturating_mul(NANOS_PER_SEC as u64)), 348 | )?; 349 | 350 | Ok(()) 351 | } 352 | 353 | /// Returns the pulse width. 354 | pub fn pulse_width(&self) -> Result { 355 | Ok(Duration::from_nanos(sysfs::pulse_width( 356 | self.chip, 357 | self.channel, 358 | )?)) 359 | } 360 | 361 | /// Sets the pulse width. 362 | /// 363 | /// `pulse_width` indicates the amount of time the PWM channel is active during a 364 | /// single period. 365 | /// 366 | /// This method will fail if `pulse_width` is longer than the current period. 367 | pub fn set_pulse_width(&self, pulse_width: Duration) -> Result<()> { 368 | sysfs::set_pulse_width( 369 | self.chip, 370 | self.channel, 371 | u64::from(pulse_width.subsec_nanos()) 372 | .saturating_add(pulse_width.as_secs().saturating_mul(NANOS_PER_SEC as u64)), 373 | )?; 374 | 375 | Ok(()) 376 | } 377 | 378 | /// Returns the frequency. 379 | /// 380 | /// `frequency` is a convenience method that calculates the frequency in hertz (Hz) 381 | /// based on the configured period. 382 | pub fn frequency(&self) -> Result { 383 | let period = sysfs::period(self.chip, self.channel)? as f64; 384 | 385 | Ok(if period == 0.0 { 386 | 0.0 387 | } else { 388 | 1.0 / (period / NANOS_PER_SEC) 389 | }) 390 | } 391 | 392 | /// Sets the frequency and duty cycle. 393 | /// 394 | /// `set_frequency` is a convenience method that converts `frequency` to a period, 395 | /// and calculates the duty cycle as a percentage of the frequency. 396 | /// 397 | /// `frequency` is specified in hertz (Hz). 398 | /// 399 | /// `duty_cycle` is specified as a floating point value between `0.0` (0%) and `1.0` (100%). 400 | pub fn set_frequency(&self, frequency: f64, duty_cycle: f64) -> Result<()> { 401 | // Set duty cycle to 0 first in case the new period is shorter than the current duty cycle 402 | let _ = sysfs::set_pulse_width(self.chip, self.channel, 0); 403 | 404 | // Convert to nanoseconds 405 | let period = if frequency == 0.0 { 406 | 0.0 407 | } else { 408 | (1.0 / frequency) * NANOS_PER_SEC 409 | }; 410 | let pulse_width = period * duty_cycle.clamp(0.0, 1.0); 411 | 412 | sysfs::set_period(self.chip, self.channel, period as u64)?; 413 | sysfs::set_pulse_width(self.chip, self.channel, pulse_width as u64)?; 414 | 415 | Ok(()) 416 | } 417 | 418 | /// Returns the duty cycle. 419 | /// 420 | /// `duty_cycle` is a convenience method that calculates the duty cycle as a 421 | /// floating point value between `0.0` (0%) and `1.0` (100%) based on the configured 422 | /// period and pulse width. 423 | pub fn duty_cycle(&self) -> Result { 424 | let period = sysfs::period(self.chip, self.channel)? as f64; 425 | let pulse_width = sysfs::pulse_width(self.chip, self.channel)? as f64; 426 | 427 | Ok(if period == 0.0 { 428 | 0.0 429 | } else { 430 | (pulse_width / period).clamp(0.0, 1.0) 431 | }) 432 | } 433 | 434 | /// Sets the duty cycle. 435 | /// 436 | /// `set_duty_cycle` is a convenience method that converts `duty_cycle` to a 437 | /// pulse width based on the configured period. 438 | /// 439 | /// `duty_cycle` is specified as a floating point value between `0.0` (0%) and `1.0` (100%). 440 | pub fn set_duty_cycle(&self, duty_cycle: f64) -> Result<()> { 441 | let period = sysfs::period(self.chip, self.channel)? as f64; 442 | let pulse_width = period * duty_cycle.clamp(0.0, 1.0); 443 | 444 | sysfs::set_pulse_width(self.chip, self.channel, pulse_width as u64)?; 445 | 446 | Ok(()) 447 | } 448 | 449 | /// Returns the polarity. 450 | pub fn polarity(&self) -> Result { 451 | Ok(sysfs::polarity(self.chip, self.channel)?) 452 | } 453 | 454 | /// Sets the polarity. 455 | /// 456 | /// `polarity` configures the active logic level as either high 457 | /// ([`Normal`]) or low ([`Inverse`]). 458 | /// 459 | /// [`Normal`]: enum.Polarity.html#variant.Normal 460 | /// [`Inverse`]: enum.Polarity.html#variant.Inverse 461 | pub fn set_polarity(&self, polarity: Polarity) -> Result<()> { 462 | sysfs::set_polarity(self.chip, self.channel, polarity)?; 463 | 464 | Ok(()) 465 | } 466 | 467 | /// Returns `true` if the PWM channel is enabled. 468 | pub fn is_enabled(&self) -> Result { 469 | Ok(sysfs::enabled(self.chip, self.channel)?) 470 | } 471 | 472 | /// Enables the PWM channel. 473 | pub fn enable(&self) -> Result<()> { 474 | sysfs::set_enabled(self.chip, self.channel, true)?; 475 | 476 | Ok(()) 477 | } 478 | 479 | /// Disables the PWM channel. 480 | pub fn disable(&self) -> Result<()> { 481 | sysfs::set_enabled(self.chip, self.channel, false)?; 482 | 483 | Ok(()) 484 | } 485 | 486 | /// Returns the value of `reset_on_drop`. 487 | pub fn reset_on_drop(&self) -> bool { 488 | self.reset_on_drop 489 | } 490 | 491 | /// When enabled, disables the PWM channel when the `Pwm` instance 492 | /// goes out of scope. By default, this is set to `true`. 493 | /// 494 | /// ## Note 495 | /// 496 | /// Drop methods aren't called when a process is abnormally terminated, for 497 | /// instance when a user presses Ctrl + C, and the `SIGINT` signal 498 | /// isn't caught. You can catch those using crates such as [`simple_signal`]. 499 | /// 500 | /// [`simple_signal`]: https://crates.io/crates/simple-signal 501 | pub fn set_reset_on_drop(&mut self, reset_on_drop: bool) { 502 | self.reset_on_drop = reset_on_drop; 503 | } 504 | } 505 | 506 | impl Drop for Pwm { 507 | fn drop(&mut self) { 508 | if self.reset_on_drop { 509 | let _ = sysfs::set_enabled(self.chip, self.channel, false); 510 | let _ = sysfs::unexport(self.chip, self.channel); 511 | } 512 | } 513 | } 514 | -------------------------------------------------------------------------------- /src/i2c/ioctl.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::io; 3 | use std::ptr; 4 | use std::result; 5 | 6 | use libc::{self, c_int, c_ulong, ioctl}; 7 | 8 | #[cfg(target_env = "gnu")] 9 | type IoctlLong = libc::c_ulong; 10 | #[cfg(target_env = "musl")] 11 | type IoctlLong = c_int; 12 | 13 | pub type Result = result::Result; 14 | 15 | // Based on i2c.h, i2c-dev.c, i2c-dev.h and the documentation at https://www.kernel.org/doc/Documentation/i2c 16 | // and http://smbus.org/specs/SMBus_3_1_20180319.pdf 17 | 18 | // Capabilities returned by REQ_FUNCS 19 | const FUNC_I2C: c_ulong = 0x01; 20 | const FUNC_10BIT_ADDR: c_ulong = 0x02; 21 | const FUNC_PROTOCOL_MANGLING: c_ulong = 0x04; 22 | const FUNC_SMBUS_PEC: c_ulong = 0x08; 23 | const FUNC_NOSTART: c_ulong = 0x10; 24 | const FUNC_SLAVE: c_ulong = 0x20; 25 | const FUNC_SMBUS_BLOCK_PROC_CALL: c_ulong = 0x8000; 26 | const FUNC_SMBUS_QUICK: c_ulong = 0x01_0000; 27 | const FUNC_SMBUS_READ_BYTE: c_ulong = 0x02_0000; 28 | const FUNC_SMBUS_WRITE_BYTE: c_ulong = 0x04_0000; 29 | const FUNC_SMBUS_READ_BYTE_DATA: c_ulong = 0x08_0000; 30 | const FUNC_SMBUS_WRITE_BYTE_DATA: c_ulong = 0x10_0000; 31 | const FUNC_SMBUS_READ_WORD_DATA: c_ulong = 0x20_0000; 32 | const FUNC_SMBUS_WRITE_WORD_DATA: c_ulong = 0x40_0000; 33 | const FUNC_SMBUS_PROC_CALL: c_ulong = 0x80_0000; 34 | const FUNC_SMBUS_READ_BLOCK_DATA: c_ulong = 0x0100_0000; 35 | const FUNC_SMBUS_WRITE_BLOCK_DATA: c_ulong = 0x0200_0000; 36 | const FUNC_SMBUS_READ_I2C_BLOCK: c_ulong = 0x0400_0000; 37 | const FUNC_SMBUS_WRITE_I2C_BLOCK: c_ulong = 0x0800_0000; 38 | const FUNC_SMBUS_HOST_NOTIFY: c_ulong = 0x1000_0000; 39 | 40 | /// Lists the features supported by the underlying drivers. 41 | #[derive(PartialEq, Eq, Copy, Clone)] 42 | pub struct Capabilities { 43 | funcs: c_ulong, 44 | } 45 | 46 | impl Capabilities { 47 | /// Constructs a new `Capabilities`. 48 | /// 49 | /// `Capabilities` indicates which I2C features and SMBus protocols 50 | /// are supported by the underlying drivers. 51 | fn new(funcs: c_ulong) -> Capabilities { 52 | Capabilities { funcs } 53 | } 54 | 55 | pub(crate) fn i2c(self) -> bool { 56 | (self.funcs & FUNC_I2C) > 0 57 | } 58 | 59 | pub(crate) fn slave(self) -> bool { 60 | (self.funcs & FUNC_SLAVE) > 0 61 | } 62 | 63 | /// Indicates whether 10-bit addresses are supported. 64 | pub fn addr_10bit(self) -> bool { 65 | (self.funcs & FUNC_10BIT_ADDR) > 0 66 | } 67 | 68 | /// Indicates whether I2C Block Read is supported. 69 | pub fn i2c_block_read(self) -> bool { 70 | (self.funcs & FUNC_SMBUS_READ_I2C_BLOCK) > 0 71 | } 72 | 73 | /// Indicates whether I2C Block Write is supported. 74 | pub fn i2c_block_write(self) -> bool { 75 | (self.funcs & FUNC_SMBUS_WRITE_I2C_BLOCK) > 0 76 | } 77 | 78 | /// Indicates whether protocol mangling is supported. 79 | pub(crate) fn protocol_mangling(self) -> bool { 80 | (self.funcs & FUNC_PROTOCOL_MANGLING) > 0 81 | } 82 | 83 | /// Indicates whether the NOSTART flag is supported. 84 | pub(crate) fn nostart(self) -> bool { 85 | (self.funcs & FUNC_NOSTART) > 0 86 | } 87 | 88 | /// Indicates whether SMBus Quick Command is supported. 89 | pub fn smbus_quick_command(self) -> bool { 90 | (self.funcs & FUNC_SMBUS_QUICK) > 0 91 | } 92 | 93 | /// Indicates whether SMBus Receive Byte is supported. 94 | pub fn smbus_receive_byte(self) -> bool { 95 | (self.funcs & FUNC_SMBUS_READ_BYTE) > 0 96 | } 97 | 98 | /// Indicates whether SMBus Send Byte is supported. 99 | pub fn smbus_send_byte(self) -> bool { 100 | (self.funcs & FUNC_SMBUS_WRITE_BYTE) > 0 101 | } 102 | 103 | /// Indicates whether SMBus Read Byte is supported. 104 | pub fn smbus_read_byte(self) -> bool { 105 | (self.funcs & FUNC_SMBUS_READ_BYTE_DATA) > 0 106 | } 107 | 108 | /// Indicates whether SMBus Write Byte is supported. 109 | pub fn smbus_write_byte(self) -> bool { 110 | (self.funcs & FUNC_SMBUS_WRITE_BYTE_DATA) > 0 111 | } 112 | 113 | /// Indicates whether SMBus Read Word is supported. 114 | pub fn smbus_read_word(self) -> bool { 115 | (self.funcs & FUNC_SMBUS_READ_WORD_DATA) > 0 116 | } 117 | 118 | /// Indicates whether SMBus Write Word is supported. 119 | pub fn smbus_write_word(self) -> bool { 120 | (self.funcs & FUNC_SMBUS_WRITE_WORD_DATA) > 0 121 | } 122 | 123 | /// Indicates whether SMBus Process Call is supported. 124 | pub fn smbus_process_call(self) -> bool { 125 | (self.funcs & FUNC_SMBUS_PROC_CALL) > 0 126 | } 127 | 128 | /// Indicates whether SMBus Block Read is supported. 129 | pub fn smbus_block_read(self) -> bool { 130 | (self.funcs & FUNC_SMBUS_READ_BLOCK_DATA) > 0 131 | } 132 | 133 | /// Indicates whether SMBus Block Write is supported. 134 | pub fn smbus_block_write(self) -> bool { 135 | (self.funcs & FUNC_SMBUS_WRITE_BLOCK_DATA) > 0 136 | } 137 | 138 | /// Indicates whether SMBus Block Process Call is supported. 139 | pub fn smbus_block_process_call(self) -> bool { 140 | (self.funcs & FUNC_SMBUS_BLOCK_PROC_CALL) > 0 141 | } 142 | 143 | /// Indicates whether SMBus Packet Error Checking is supported. 144 | pub fn smbus_pec(self) -> bool { 145 | (self.funcs & FUNC_SMBUS_PEC) > 0 146 | } 147 | 148 | /// Indicates whether SMBus Host Notify is supported. 149 | pub fn smbus_host_notify(self) -> bool { 150 | (self.funcs & FUNC_SMBUS_HOST_NOTIFY) > 0 151 | } 152 | } 153 | 154 | impl fmt::Debug for Capabilities { 155 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 156 | f.debug_struct("Capabilities") 157 | .field("addr_10bit", &self.addr_10bit()) 158 | .field("i2c_block_read", &self.i2c_block_read()) 159 | .field("i2c_block_write", &self.i2c_block_write()) 160 | .field("smbus_quick_command", &self.smbus_quick_command()) 161 | .field("smbus_receive_byte", &self.smbus_receive_byte()) 162 | .field("smbus_send_byte", &self.smbus_send_byte()) 163 | .field("smbus_read_byte", &self.smbus_read_byte()) 164 | .field("smbus_write_byte", &self.smbus_write_byte()) 165 | .field("smbus_read_word", &self.smbus_read_word()) 166 | .field("smbus_write_word", &self.smbus_write_word()) 167 | .field("smbus_process_call", &self.smbus_process_call()) 168 | .field("smbus_block_read", &self.smbus_block_read()) 169 | .field("smbus_block_write", &self.smbus_block_write()) 170 | .field("smbus_block_process_call", &self.smbus_block_process_call()) 171 | .field("smbus_pec", &self.smbus_pec()) 172 | .field("smbus_host_notify", &self.smbus_host_notify()) 173 | .finish() 174 | } 175 | } 176 | 177 | // ioctl() requests supported by i2cdev 178 | const REQ_RETRIES: IoctlLong = 0x0701; // How many retries when waiting for an ACK 179 | const REQ_TIMEOUT: IoctlLong = 0x0702; // Timeout in 10ms units 180 | const REQ_SLAVE: IoctlLong = 0x0706; // Set slave address 181 | const REQ_SLAVE_FORCE: IoctlLong = 0x0703; // Set slave address, even if it's already in use by a driver 182 | const REQ_TENBIT: IoctlLong = 0x0704; // Use 10-bit slave addresses 183 | const REQ_FUNCS: IoctlLong = 0x0705; // Read I2C bus capabilities 184 | const REQ_RDWR: IoctlLong = 0x0707; // Combined read/write transfer with a single STOP 185 | const REQ_PEC: IoctlLong = 0x0708; // SMBus: Use Packet Error Checking 186 | const REQ_SMBUS: IoctlLong = 0x0720; // SMBus: Transfer data 187 | 188 | // NOTE: REQ_RETRIES - Supported in i2cdev, but not used in the underlying drivers 189 | // NOTE: REQ_RDWR - Only a single read operation is supported as the final message (see i2c-bcm2835.c) 190 | 191 | const RDWR_FLAG_RD: u16 = 0x0001; // Read operation 192 | const RDWR_FLAG_TEN: u16 = 0x0010; // 10-bit slave address 193 | 194 | const RDWR_MSG_MAX: usize = 42; // Maximum messages per RDWR operation 195 | const SMBUS_BLOCK_MAX: usize = 32; // Maximum bytes per block transfer 196 | 197 | // SMBus read or write request 198 | #[derive(Debug, PartialEq, Copy, Clone)] 199 | enum SmbusReadWrite { 200 | Read = 1, 201 | Write = 0, 202 | } 203 | 204 | // Size/Type identifiers for the data contained in SmbusBuffer 205 | #[derive(Debug, PartialEq, Copy, Clone)] 206 | enum SmbusSize { 207 | Quick = 0, 208 | Byte = 1, 209 | ByteData = 2, 210 | WordData = 3, 211 | ProcCall = 4, 212 | BlockData = 5, 213 | I2cBlockData = 8, 214 | } 215 | 216 | // Holds data transferred by REQ_SMBUS requests. Data can either consist of a 217 | // single byte, a 16-bit word, or a block, where the first byte contains the length, 218 | // followed by up to 32 bytes of data, with the final byte used as padding. 219 | #[derive(Copy, Clone)] 220 | #[repr(C)] 221 | struct SmbusBuffer { 222 | data: [u8; SMBUS_BLOCK_MAX + 2], 223 | } 224 | 225 | impl SmbusBuffer { 226 | pub fn new() -> SmbusBuffer { 227 | SmbusBuffer { 228 | data: [0u8; SMBUS_BLOCK_MAX + 2], 229 | } 230 | } 231 | 232 | pub fn with_byte(value: u8) -> SmbusBuffer { 233 | let mut buffer = SmbusBuffer { 234 | data: [0u8; SMBUS_BLOCK_MAX + 2], 235 | }; 236 | 237 | buffer.data[0] = value; 238 | 239 | buffer 240 | } 241 | 242 | pub fn with_word(value: u16) -> SmbusBuffer { 243 | let mut buffer = SmbusBuffer { 244 | data: [0u8; SMBUS_BLOCK_MAX + 2], 245 | }; 246 | 247 | // Low byte is sent first (SMBus 3.1 spec @ 6.5.4) 248 | buffer.data[0] = (value & 0xFF) as u8; 249 | buffer.data[1] = (value >> 8) as u8; 250 | 251 | buffer 252 | } 253 | 254 | pub fn with_buffer(value: &[u8]) -> SmbusBuffer { 255 | let mut buffer = SmbusBuffer { 256 | data: [0u8; SMBUS_BLOCK_MAX + 2], 257 | }; 258 | 259 | buffer.data[0] = if value.len() > SMBUS_BLOCK_MAX { 260 | buffer.data[1..=SMBUS_BLOCK_MAX].copy_from_slice(&value[..SMBUS_BLOCK_MAX]); 261 | SMBUS_BLOCK_MAX as u8 262 | } else { 263 | buffer.data[1..=value.len()].copy_from_slice(value); 264 | value.len() as u8 265 | }; 266 | 267 | buffer 268 | } 269 | } 270 | 271 | // Specifies SMBus request parameters 272 | #[repr(C)] 273 | struct SmbusRequest { 274 | // Read (1) or write (0) request. 275 | read_write: u8, 276 | // User-specified 8-bit command value. 277 | command: u8, 278 | // Request type identifier. 279 | size: u32, 280 | // Pointer to buffer, or 0. 281 | data: *mut SmbusBuffer, 282 | } 283 | 284 | fn smbus_request( 285 | fd: c_int, 286 | read_write: SmbusReadWrite, 287 | command: u8, 288 | size: SmbusSize, 289 | data: Option<&mut SmbusBuffer>, 290 | ) -> Result<()> { 291 | let mut request = SmbusRequest { 292 | read_write: read_write as u8, 293 | command, 294 | size: size as u32, 295 | data: if let Some(buffer) = data { 296 | buffer 297 | } else { 298 | ptr::null_mut() 299 | }, 300 | }; 301 | 302 | parse_retval!(unsafe { ioctl(fd, REQ_SMBUS, &mut request) })?; 303 | 304 | Ok(()) 305 | } 306 | 307 | pub fn smbus_quick_command(fd: c_int, value: bool) -> Result<()> { 308 | // Quick Command uses the read_write field, instead of the data buffer 309 | smbus_request( 310 | fd, 311 | if value { 312 | SmbusReadWrite::Read 313 | } else { 314 | SmbusReadWrite::Write 315 | }, 316 | 0, 317 | SmbusSize::Quick, 318 | None, 319 | ) 320 | } 321 | 322 | pub fn smbus_receive_byte(fd: c_int) -> Result { 323 | let mut buffer = SmbusBuffer::new(); 324 | smbus_request( 325 | fd, 326 | SmbusReadWrite::Read, 327 | 0, 328 | SmbusSize::Byte, 329 | Some(&mut buffer), 330 | )?; 331 | 332 | Ok(buffer.data[0]) 333 | } 334 | 335 | pub fn smbus_send_byte(fd: c_int, value: u8) -> Result<()> { 336 | // Send Byte uses the command field, instead of the data buffer 337 | smbus_request(fd, SmbusReadWrite::Write, value, SmbusSize::Byte, None) 338 | } 339 | 340 | pub fn smbus_read_byte(fd: c_int, command: u8) -> Result { 341 | let mut buffer = SmbusBuffer::new(); 342 | smbus_request( 343 | fd, 344 | SmbusReadWrite::Read, 345 | command, 346 | SmbusSize::ByteData, 347 | Some(&mut buffer), 348 | )?; 349 | 350 | Ok(buffer.data[0]) 351 | } 352 | 353 | pub fn smbus_read_word(fd: c_int, command: u8) -> Result { 354 | let mut buffer = SmbusBuffer::new(); 355 | smbus_request( 356 | fd, 357 | SmbusReadWrite::Read, 358 | command, 359 | SmbusSize::WordData, 360 | Some(&mut buffer), 361 | )?; 362 | 363 | // Low byte is received first (SMBus 3.1 spec @ 6.5.5) 364 | Ok(u16::from(buffer.data[0]) | (u16::from(buffer.data[1]) << 8)) 365 | } 366 | 367 | pub fn smbus_write_byte(fd: c_int, command: u8, value: u8) -> Result<()> { 368 | let mut buffer = SmbusBuffer::with_byte(value); 369 | smbus_request( 370 | fd, 371 | SmbusReadWrite::Write, 372 | command, 373 | SmbusSize::ByteData, 374 | Some(&mut buffer), 375 | ) 376 | } 377 | 378 | pub fn smbus_write_word(fd: c_int, command: u8, value: u16) -> Result<()> { 379 | let mut buffer = SmbusBuffer::with_word(value); 380 | smbus_request( 381 | fd, 382 | SmbusReadWrite::Write, 383 | command, 384 | SmbusSize::WordData, 385 | Some(&mut buffer), 386 | ) 387 | } 388 | 389 | pub fn smbus_process_call(fd: c_int, command: u8, value: u16) -> Result { 390 | let mut buffer = SmbusBuffer::with_word(value); 391 | smbus_request( 392 | fd, 393 | SmbusReadWrite::Write, 394 | command, 395 | SmbusSize::ProcCall, 396 | Some(&mut buffer), 397 | )?; 398 | 399 | // Low byte is received first (SMBus 3.1 spec @ 6.5.6) 400 | Ok(u16::from(buffer.data[0]) | (u16::from(buffer.data[1]) << 8)) 401 | } 402 | 403 | pub fn smbus_block_read(fd: c_int, command: u8, value: &mut [u8]) -> Result { 404 | let mut buffer = SmbusBuffer::new(); 405 | smbus_request( 406 | fd, 407 | SmbusReadWrite::Read, 408 | command, 409 | SmbusSize::BlockData, 410 | Some(&mut buffer), 411 | )?; 412 | 413 | // Verify the length in case we're receiving corrupted data 414 | let incoming_length = if buffer.data[0] as usize > SMBUS_BLOCK_MAX { 415 | SMBUS_BLOCK_MAX 416 | } else { 417 | buffer.data[0] as usize 418 | }; 419 | 420 | // Make sure the incoming data fits in the value buffer 421 | let value_length = value.len(); 422 | if incoming_length > value_length { 423 | value.copy_from_slice(&buffer.data[1..=value_length]); 424 | } else { 425 | value[..incoming_length].copy_from_slice(&buffer.data[1..=incoming_length]); 426 | } 427 | 428 | Ok(incoming_length) 429 | } 430 | 431 | pub fn smbus_block_write(fd: c_int, command: u8, value: &[u8]) -> Result<()> { 432 | let mut buffer = SmbusBuffer::with_buffer(value); 433 | smbus_request( 434 | fd, 435 | SmbusReadWrite::Write, 436 | command, 437 | SmbusSize::BlockData, 438 | Some(&mut buffer), 439 | ) 440 | } 441 | 442 | pub fn i2c_block_read(fd: c_int, command: u8, value: &mut [u8]) -> Result<()> { 443 | let mut buffer = SmbusBuffer::new(); 444 | buffer.data[0] = if value.len() > SMBUS_BLOCK_MAX { 445 | SMBUS_BLOCK_MAX as u8 446 | } else { 447 | value.len() as u8 448 | }; 449 | 450 | smbus_request( 451 | fd, 452 | SmbusReadWrite::Read, 453 | command, 454 | SmbusSize::I2cBlockData, 455 | Some(&mut buffer), 456 | )?; 457 | 458 | value[..buffer.data[0] as usize].copy_from_slice(&buffer.data[1..=buffer.data[0] as usize]); 459 | 460 | Ok(()) 461 | } 462 | 463 | pub fn i2c_block_write(fd: c_int, command: u8, value: &[u8]) -> Result<()> { 464 | let mut buffer = SmbusBuffer::with_buffer(value); 465 | smbus_request( 466 | fd, 467 | SmbusReadWrite::Write, 468 | command, 469 | SmbusSize::I2cBlockData, 470 | Some(&mut buffer), 471 | ) 472 | } 473 | 474 | // Specifies RDWR segment parameters 475 | #[repr(C)] 476 | #[derive(Debug, PartialEq, Copy, Clone)] 477 | struct RdwrSegment { 478 | // Slave address 479 | addr: u16, 480 | // Segment flags 481 | flags: u16, 482 | // Buffer length 483 | len: u16, 484 | // Pointer to buffer 485 | data: usize, 486 | } 487 | 488 | // Specifies RWDR request parameters 489 | #[repr(C)] 490 | #[derive(Debug, PartialEq, Copy, Clone)] 491 | struct RdwrRequest { 492 | // Pointer to an array of segments 493 | segments: *mut [RdwrSegment], 494 | // Number of segments 495 | nmsgs: u32, 496 | } 497 | 498 | pub fn i2c_write_read( 499 | fd: c_int, 500 | address: u16, 501 | addr_10bit: bool, 502 | write_buffer: &[u8], 503 | read_buffer: &mut [u8], 504 | ) -> Result<()> { 505 | // 0 length buffers may cause issues 506 | if write_buffer.is_empty() || read_buffer.is_empty() { 507 | return Ok(()); 508 | } 509 | 510 | let segment_write = RdwrSegment { 511 | addr: address, 512 | flags: if addr_10bit { RDWR_FLAG_TEN } else { 0 }, 513 | len: write_buffer.len() as u16, 514 | data: write_buffer.as_ptr() as usize, 515 | }; 516 | 517 | let segment_read = RdwrSegment { 518 | addr: address, 519 | flags: if addr_10bit { 520 | RDWR_FLAG_RD | RDWR_FLAG_TEN 521 | } else { 522 | RDWR_FLAG_RD 523 | }, 524 | len: read_buffer.len() as u16, 525 | data: read_buffer.as_mut_ptr() as usize, 526 | }; 527 | 528 | let mut segments: [RdwrSegment; 2] = [segment_write, segment_read]; 529 | let mut request = RdwrRequest { 530 | segments: &mut segments, 531 | nmsgs: 2, 532 | }; 533 | 534 | parse_retval!(unsafe { ioctl(fd, REQ_RDWR, &mut request) })?; 535 | 536 | Ok(()) 537 | } 538 | 539 | pub fn set_slave_address(fd: c_int, value: c_ulong) -> Result<()> { 540 | parse_retval!(unsafe { ioctl(fd, REQ_SLAVE, value) })?; 541 | 542 | Ok(()) 543 | } 544 | 545 | pub fn set_addr_10bit(fd: c_int, value: c_ulong) -> Result<()> { 546 | parse_retval!(unsafe { ioctl(fd, REQ_TENBIT, value) })?; 547 | 548 | Ok(()) 549 | } 550 | 551 | pub fn set_pec(fd: c_int, value: c_ulong) -> Result<()> { 552 | parse_retval!(unsafe { ioctl(fd, REQ_PEC, value) })?; 553 | 554 | Ok(()) 555 | } 556 | 557 | pub fn set_timeout(fd: c_int, value: c_ulong) -> Result<()> { 558 | // Timeout is specified in units of 10ms 559 | let timeout: c_ulong = if value > 0 && value < 10 { 560 | 1 561 | } else { 562 | value / 10 563 | }; 564 | 565 | parse_retval!(unsafe { ioctl(fd, REQ_TIMEOUT, timeout) })?; 566 | 567 | Ok(()) 568 | } 569 | 570 | pub fn set_retries(fd: c_int, value: c_ulong) -> Result<()> { 571 | // Number of retries on arbitration loss 572 | parse_retval!(unsafe { ioctl(fd, REQ_RETRIES, value) })?; 573 | 574 | Ok(()) 575 | } 576 | 577 | pub fn funcs(fd: c_int) -> Result { 578 | let mut funcs: c_ulong = 0; 579 | 580 | parse_retval!(unsafe { ioctl(fd, REQ_FUNCS, &mut funcs) })?; 581 | 582 | Ok(Capabilities::new(funcs)) 583 | } 584 | --------------------------------------------------------------------------------