pwm_pca9685/
lib.rs

1//! This is a platform agnostic Rust driver for the PCA9685 PWM/Servo/LED
2//! controller, based on the [`embedded-hal`] traits.
3//!
4//! [`embedded-hal`]: https://github.com/rust-embedded/embedded-hal
5//!
6//! This driver allows you to:
7//! - Enable/disable the device. See: [`enable()`](Pca9685::enable).
8//! - Set the _on_ and _off_ counter for a channel or all of them. See: [`set_channel_on()`](Pca9685::set_channel_on).
9//! - Set the _on_ and _off_ counters for a channel or all of them at once. See: [`set_channel_on_off()`](Pca9685::set_channel_on_off).
10//! - Set a channel to be always on or off. See: [`set_channel_full_on()`](Pca9685::set_channel_full_on).
11//! - Set the _on_ and _off_ counters for each channel at once. See: [`set_all_on_off()`](Pca9685::set_all_on_off).
12//! - Set the _on_ and _off_ counters **and** the always-on/always-off flags for each channel at once. See: [`set_all_channels()`](Pca9685::set_all_channels).
13//! - Set the prescale value. See: [`set_prescale()`](Pca9685::set_prescale).
14//! - Select the output logic state direct or inverted. See: [`set_output_logic_state()`](Pca9685::set_output_logic_state).
15//! - Set when the outputs change. See: [`set_output_change_behavior()`](Pca9685::set_output_change_behavior).
16//! - Set the output driver configuration. See: [`set_output_driver()`](Pca9685::set_output_driver).
17//! - Set the output value when outputs are disabled. See: [`set_disabled_output_value()`](Pca9685::set_disabled_output_value)
18//! - Select the EXTCLK pin as clock source. See: [`use_external_clock()`](Pca9685::use_external_clock).
19//! - Enable/disable a programmable address. See: [`enable_programmable_address()`](Pca9685::enable_programmable_address).
20//! - Set a programmable address. See: [`set_programmable_address()`](Pca9685::set_programmable_address).
21//! - Change the address used by the driver. See: [`set_address()`](Pca9685::set_address).
22//! - Restart keeping the PWM register contents. See: [`enable_restart_and_disable()`](Pca9685::enable_restart_and_disable).
23//!
24//! [Introductory blog post](https://blog.eldruin.com/pca9685-pwm-led-servo-controller-driver-in-rust/)
25//!
26//! ## The device
27//!
28//! This device is an I2C-bus controlled 16-channel, 12-bit PWM controller.
29//! Its outputs can be used to control servo motors or LEDs, for example.
30//!
31//! Each channel output has its own 12-bit resolution (4096 steps) fixed
32//! frequency individual PWM controller that operates at a programmable
33//! frequency from a typical of 24 Hz to 1526 Hz with a duty cycle that is
34//! adjustable from 0% to 100%.
35//! All outputs are set to the same PWM frequency.
36//!
37//! Each channel output can be off or on (no PWM control), or set at its
38//! individual PWM controller value. The output driver is programmed to be
39//! either open-drain with a 25 mA current sink capability at 5 V or totem pole
40//! with a 25 mA sink, 10 mA source capability at 5 V. The PCA9685 operates
41//! with a supply voltage range of 2.3 V to 5.5 V and the inputs and outputs
42//! are 5.5 V tolerant. LEDs can be directly connected to the outputs (up to
43//! 25 mA, 5.5 V) or controlled with external drivers and a minimum amount of
44//! discrete components for larger current, higher voltage LEDs, etc.
45//! It is optimized to be used as an LED controller for Red/Green/Blue/Amber
46//! (RGBA) color backlighting applications.
47//!
48//! Datasheet: [PCA9685](https://www.nxp.com/docs/en/data-sheet/PCA9685.pdf)
49//!
50//! ## Usage examples (see also examples folder)
51//!
52//! To use this driver, import this crate and an `embedded_hal` implementation,
53//! then instantiate the appropriate device.
54//!
55//! Please find additional examples in this repository: [driver-examples]
56//!
57//! [driver-examples]: https://github.com/eldruin/driver-examples
58//!
59//! ### Create a driver instance
60//!
61//! ```no_run
62//! use linux_embedded_hal::I2cdev;
63//! use pwm_pca9685::{Address, Pca9685};
64//!
65//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
66//! let address = Address::default();
67//! let pwm = Pca9685::new(dev, address).unwrap();
68//! // do something...
69//!
70//! // get the I2C device back
71//! let dev = pwm.destroy();
72//! ```
73//!
74//! ### Create a driver instance for the PCA9685 with an alternative address
75//!
76//! ```no_run
77//! use linux_embedded_hal::I2cdev;
78//! use pwm_pca9685::{Address, Pca9685};
79//!
80//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
81//! let (a5, a4, a3, a2, a1, a0) = (false, true, false, true, true, false);
82//! let address = (a5, a4, a3, a2, a1, a0);
83//! let pwm = Pca9685::new(dev, address).unwrap();
84//! ```
85//!
86//! ### Set the PWM frequency and channel duty cycles
87//!
88//! - Set a PWM frequency of 60 Hz (corresponds to a value of 100 for the
89//!   prescale).
90//! - Set a duty cycle of 50% for channel 0.
91//! - Set a duty cycle of 75% for channel 1 delayed 814 µs with respect
92//!   to channel 0.
93//!
94//! ```no_run
95//! use linux_embedded_hal::I2cdev;
96//! use pwm_pca9685::{Address, Channel, Pca9685};
97//!
98//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
99//! let address = Address::default();
100//! let mut pwm = Pca9685::new(dev, address).unwrap();
101//! pwm.set_prescale(100).unwrap();
102//! pwm.enable().unwrap();
103//!
104//! // Turn on channel 0 at 0 and off at 2047, which is 50% in the range `[0..4095]`.
105//! pwm.set_channel_on_off(Channel::C0, 0, 2047).unwrap();
106//!
107//! // Turn on channel 1 at 200, then off at 3271. These values comes from:
108//! // 0.000814 (seconds) * 60 (Hz) * 4096 (resolution) = 200
109//! // 4096 * 0.75 + 200 = 3272
110//! pwm.set_channel_on_off(Channel::C1, 200, 3272).unwrap();
111//! ```
112//!
113//! ### Set the PWM frequency and channel duty cycles separately
114//!
115//! - Set a PWM frequency of 60 Hz (corresponds to a value of 100 for the
116//!   prescale).
117//! - Set a duty cycle of 50% for channel 0.
118//! - Set a duty cycle of 75% for channel 1 delayed 814 µs with respect
119//!   to channel 0.
120//!
121//! ```no_run
122//! use linux_embedded_hal::I2cdev;
123//! use pwm_pca9685::{Address, Channel, Pca9685};
124//!
125//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
126//! let address = Address::default();
127//! let mut pwm = Pca9685::new(dev, address).unwrap();
128//! pwm.set_prescale(100).unwrap();
129//! pwm.enable().unwrap();
130//!
131//! // Turn on channel 0 at 0
132//! pwm.set_channel_on(Channel::C0, 0).unwrap();
133//!
134//! // Turn off channel 0 at 2047, which is 50% in the range `[0..4095]`.
135//! pwm.set_channel_off(Channel::C0, 2047).unwrap();
136//!
137//! // Turn on channel 1 at 200. This value comes from:
138//! // 0.000814 (seconds) * 60 (Hz) * 4096 (resolution) = 200
139//! pwm.set_channel_on(Channel::C1, 200).unwrap();
140//!
141//! // Turn off channel 1 at 3271, which is 75% in the range `[0..4095]`
142//! // plus 200 which is when the channel turns on.
143//! pwm.set_channel_off(Channel::C1, 3271).unwrap();
144//! ```
145//!
146//! ### Set a channel completely on and off (beware of precedences).
147//!
148//! ```no_run
149//! use linux_embedded_hal::I2cdev;
150//! use pwm_pca9685::{Address, Channel, Pca9685};
151//!
152//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
153//! let address = Address::default();
154//! let mut pwm = Pca9685::new(dev, address).unwrap();
155//! pwm.enable().unwrap();
156//!
157//! // Turn channel 0 full on at 1024
158//! pwm.set_channel_full_on(Channel::C0, 1024).unwrap();
159//!
160//! // Turn channel 0 full off (full off takes precedence over on settings)
161//! pwm.set_channel_full_off(Channel::C0).unwrap();
162//!
163//! // Return channel 0 to full on by deactivating full off.
164//! // The value is ignored because full on takes precedence
165//! // over off settings except full off.
166//! let value_ignored_for_now = 2048;
167//! pwm.set_channel_off(Channel::C0, value_ignored_for_now).unwrap();
168//!
169//! // Deactivate full on and set a duty cycle of 50% for channel 0.
170//! // (on from 0 to 2047, then off)
171//! pwm.set_channel_on(Channel::C0, 0).unwrap();
172//! ```
173//!
174//! ### Set a 50% duty cycle for all channels at once
175//!
176//! ```no_run
177//! use linux_embedded_hal::I2cdev;
178//! use pwm_pca9685::{ Channel, Pca9685, Address };
179//!
180//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
181//! let address = Address::default();
182//! let mut pwm = Pca9685::new(dev, address).unwrap();
183//! pwm.enable().unwrap();
184//!
185//! let mut on = [0; 16];
186//! let mut off = [2047; 16];
187//! pwm.set_all_on_off(&on, &off);
188//! ```
189//!
190//! ### Set all channels to full on, turning on after 50% of the first cycle
191//!
192//! ```no_run
193//! use linux_embedded_hal::I2cdev;
194//! use pwm_pca9685::{Address, ChannelOnOffControl, Pca9685};
195//!
196//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
197//! let address = Address::default();
198//! let mut pwm = Pca9685::new(dev, address).unwrap();
199//! pwm.enable().unwrap();
200//! let values = [ChannelOnOffControl {
201//!     on: 2047,
202//!     full_on: true,
203//!     ..Default::default()
204//! }; 16];
205//! pwm.set_all_channels(&values);
206//! ```
207//!
208//! ### Use a programmable address
209//!
210//! Several additional addresses can be programmed for the device (they are
211//! volatile, though).
212//! Once set it is necessary to enable them so that the device responds to
213//! them. Then it is possible to change the address that the driver uses
214//! to communicate with the device.
215//!
216//! ```no_run
217//! use linux_embedded_hal::I2cdev;
218//! use pwm_pca9685::{Channel, Pca9685, Address, ProgrammableAddress};
219//!
220//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
221//! let hardware_address = Address::default();
222//! let mut pwm = Pca9685::new(dev, hardware_address).unwrap();
223//!
224//! let subaddr1 = 0x71;
225//! pwm.set_programmable_address(ProgrammableAddress::Subaddress1, subaddr1).unwrap();
226//! pwm.enable_programmable_address(ProgrammableAddress::Subaddress1).unwrap();
227//!
228//! // Now communicate using the new address:
229//! pwm.set_address(subaddr1).unwrap();
230//! pwm.enable().unwrap();
231//! pwm.set_channel_on_off(Channel::C0, 0, 2047).unwrap();
232//!
233//! // The device will also respond to the hardware address:
234//! pwm.set_address(hardware_address).unwrap();
235//! pwm.set_channel_on_off(Channel::C0, 2047, 4095).unwrap();
236//!
237//! // when done you can also disable responding to the additional address:
238//! pwm.disable_programmable_address(ProgrammableAddress::Subaddress1).unwrap();
239//! ```
240//!
241//! ### Put the device to sleep then restart previously active PWM channels
242//!
243//! ```no_run
244//! use linux_embedded_hal::{Delay, I2cdev};
245//! use pwm_pca9685::{Address, Channel, Pca9685};
246//!
247//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
248//! let mut pwm = Pca9685::new(dev, Address::default()).unwrap();
249//! pwm.enable().unwrap();
250//!
251//! pwm.set_channel_on_off(Channel::C0, 0, 2047).unwrap();
252//! // Prepare for restart and put the device to sleep
253//! pwm.enable_restart_and_disable().unwrap();
254//! // ...
255//! // re-enable device and reactivate channel 0
256//! let mut delay = Delay{};
257//! pwm.restart(&mut delay).unwrap();
258//! ```
259//!
260//! ### Using async driver
261//!
262//! Enable the `async` feature in your `Cargo.toml`:
263//! ```toml
264//! pwm-pca9685 = { version = "1.0.0", features = ["async"] }
265//! ```
266//! - Set a PWM frequency of 60 Hz (corresponds to a value of 100 for the
267//!   prescale).
268//! - Set a duty cycle of 50% for channel 0.
269//! - Set a duty cycle of 75% for channel 1 delayed 814 µs with respect
270//!   to channel 0.
271//!
272//! ```!compile_fail
273//! #![no_std]
274//! #![no_main]
275//!
276//! use embassy_executor::Spawner;
277//! use embassy_rp::{bind_interrupts, i2c};
278//! use embassy_rp::peripherals::I2C0;
279//! use pwm_pca9685::{Address, Channel, Pca9685};
280//!
281//! bind_interrupts!(struct Irqs {
282//!     I2C0_IRQ => i2c::InterruptHandler<I2C0>;
283//! });
284//!
285//! #[embassy_executor::main]
286//! async fn main(_spawner: Spawner) {
287//!     let p = embassy_rp::init(Default::default());
288//!     let dev = i2c::I2c::new_async(p.I2C0, p.PIN_1, p.PIN_0, Irqs, i2c::Config::default());
289//!     let address = Address::default();
290//!     let mut pwm = Pca9685::new(dev, address).await.unwrap();
291//!     pwm.set_prescale(100).await.unwrap();
292//!     pwm.enable().await.unwrap();
293//!
294//!     // Turn on channel 0 at 0 and off at 2047, which is 50% in the range `[0..4095]`.
295//!     pwm.set_channel_on_off(Channel::C0, 0, 2047).await.unwrap();
296//!
297//!     // Turn on channel 1 at 200, then off at 3271. These values comes from:
298//!     // 0.000814 (seconds) * 60 (Hz) * 4096 (resolution) = 200
299//!     // 4096 * 0.75 + 200 = 3272
300//!     pwm.set_channel_on_off(Channel::C1, 200, 3272).await.unwrap();
301//! }
302//! ```
303
304#![deny(missing_docs, unsafe_code)]
305#![no_std]
306
307mod config;
308mod register_access;
309use crate::register_access::Register;
310mod channels;
311mod device_impl;
312mod types;
313pub use crate::types::{
314    Address, Channel, ChannelOnOffControl, DisabledOutputValue, Error, OutputDriver,
315    OutputLogicState, OutputStateChange, Pca9685, ProgrammableAddress,
316};
317pub use nb;