Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add set_all_channels() for setting on, off counter values and full-on and full-off in one transaction #18

Merged
merged 6 commits into from
Apr 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Added `Display`, `Error` and common trait implementations for `Error<E>`.
- Added common trait implementations for types.
- Async support based on `embedded-hal-async` 1.0 behind `async` feature flag.
- Added `set_all_channels()`.

### Changed
- [breaking-change] Removed `Default` implementation for `Pca9685` struct.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ This driver allows you to:
- Set the _on_ and _off_ counters for a channel or all of them at once. See: `set_channel_on_off()`.
- Set a channel to be always on or off. See: `set_channel_full_on()`.
- Set the _on_ and _off_ counters for each channel at once. See: `set_all_on_off()`.
- Set the _on_ and _off_ counters **and** the always-on/always-off flags for
each channel at once. See: `set_all_channels()`.
- Set the prescale value. See: `set_prescale()`.
- Select the output logic state direct or inverted. See: `set_output_logic_state()`.
- Set when the outputs change. See: `set_output_change_behavior()`.
Expand Down
35 changes: 34 additions & 1 deletion src/channels.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Channel, Error, Pca9685, Register};
use crate::{types::ChannelOnOffControl, Channel, Error, Pca9685, Register};

#[cfg(not(feature = "async"))]
use embedded_hal::i2c::I2c;
Expand Down Expand Up @@ -118,6 +118,39 @@ where
.await
.map_err(Error::I2C)
}

/// Set the PWM control registers for each channel at once.
///
/// This allows to set all `on` and `off` counter values, as well as the
/// full-on and full-off bit in a single I2C transaction.
/// The index of the value in the array corresponds to the channel: 0-15.
///
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub async fn set_all_channels(
&mut self,
values: &[ChannelOnOffControl; 16],
) -> Result<(), Error<E>> {
const FULL_ON_OFF: u8 = 0b0001_0000;
let mut data = [0; 65];
data[0] = Register::C0_ON_L;
for (i, channel_value) in values.iter().enumerate() {
if channel_value.on > 4095 || channel_value.off > 4095 {
return Err(Error::InvalidInputData);
}
data[i * 4 + 1] = channel_value.on as u8;
data[i * 4 + 2] =
(channel_value.on >> 8) as u8 | (FULL_ON_OFF * channel_value.full_on as u8);
data[i * 4 + 3] = channel_value.off as u8;
data[i * 4 + 4] =
(channel_value.off >> 8) as u8 | (FULL_ON_OFF * channel_value.full_off as u8);
}
self.enable_auto_increment().await?;
self.i2c
.write(self.address, &data)
.await
.map_err(Error::I2C)
}
}

macro_rules! get_register {
Expand Down
23 changes: 21 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
//! - 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).
//! - Set a channel to be always on or off. See: [`set_channel_full_on()`](Pca9685::set_channel_full_on).
//! - Set the _on_ and _off_ counters for each channel at once. See: [`set_all_on_off()`](Pca9685::set_all_on_off).
//! - 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).
//! - Set the prescale value. See: [`set_prescale()`](Pca9685::set_prescale).
//! - Select the output logic state direct or inverted. See: [`set_output_logic_state()`](Pca9685::set_output_logic_state).
//! - Set when the outputs change. See: [`set_output_change_behavior()`](Pca9685::set_output_change_behavior).
Expand Down Expand Up @@ -186,6 +187,24 @@
//! pwm.set_all_on_off(&on, &off);
//! ```
//!
//! ### Set all channels to full on, turning on after 50% of the first cycle
//!
//! ```no_run
//! use linux_embedded_hal::I2cdev;
//! use pwm_pca9685::{Address, ChannelOnOffControl, Pca9685};
//!
//! let dev = I2cdev::new("/dev/i2c-1").unwrap();
//! let address = Address::default();
//! let mut pwm = Pca9685::new(dev, address).unwrap();
//! pwm.enable().unwrap();
//! let values = [ChannelOnOffControl {
//! on: 2047,
//! full_on: true,
//! ..Default::default()
//! }; 16];
//! pwm.set_all_channels(&values);
//! ```
//!
//! ### Use a programmable address
//!
//! Several additional addresses can be programmed for the device (they are
Expand Down Expand Up @@ -292,7 +311,7 @@ mod channels;
mod device_impl;
mod types;
pub use crate::types::{
Address, Channel, DisabledOutputValue, Error, OutputDriver, OutputLogicState,
OutputStateChange, Pca9685, ProgrammableAddress,
Address, Channel, ChannelOnOffControl, DisabledOutputValue, Error, OutputDriver,
OutputLogicState, OutputStateChange, Pca9685, ProgrammableAddress,
};
pub use nb;
13 changes: 13 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,19 @@ impl From<(bool, bool, bool, bool, bool, bool)> for Address {
}
}

/// PWM control values for a single channel
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ChannelOnOffControl {
/// Counter value to switch the channel on during each PWM cycle
pub on: u16,
/// Counter value to switch the channel off during each PWM cycle
pub off: u16,
/// Set the channel to full-on. In this case, the `off` value is ignored.
pub full_on: bool,
/// Set the channel to full-off. Takes precedence over `on` an `full_on`.
pub full_off: bool,
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
199 changes: 198 additions & 1 deletion tests/channels.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use embedded_hal_mock::eh1::i2c::Transaction as I2cTrans;
use pwm_pca9685::Channel;
use pwm_pca9685::{Channel, ChannelOnOffControl};
use std::convert::TryFrom;

mod common;
Expand Down Expand Up @@ -95,6 +95,24 @@ invalid_test!(
&[4096; 16]
);

invalid_test!(
cannot_set_all_channels_invalid_value_on,
set_all_channels,
&[ChannelOnOffControl {
on: 4096,
..Default::default()
}; 16]
);

invalid_test!(
cannot_set_all_channels_invalid_value_off,
set_all_channels,
&[ChannelOnOffControl {
off: 4096,
..Default::default()
}; 16]
);

#[test]
fn sets_autoincrement_just_once() {
let trans = [
Expand Down Expand Up @@ -360,3 +378,182 @@ fn can_set_all_on_off() {
pwm.set_all_on_off(&on, &off).unwrap();
destroy(pwm);
}

#[test]

fn can_set_all_channels() {
const FULL_ON_OFF: u8 = 0b0001_0000;
let trans = [
I2cTrans::write(DEV_ADDR, vec![Register::MODE1, MODE1_AI]),
I2cTrans::write(
DEV_ADDR,
vec![
Register::C0_ON_L,
1,
1,
3,
3,
2,
1,
4,
3,
3,
1,
5,
3,
4,
1,
6,
3,
5,
1,
7,
3,
6,
1,
8,
3,
7,
1,
9,
3,
8,
1,
0,
4,
9,
1,
1,
4,
0,
2,
2,
4,
1,
2,
3,
4,
2,
2,
4,
4,
3,
2,
5,
4,
4,
2,
6,
4 | FULL_ON_OFF,
5,
2 | FULL_ON_OFF,
7,
4,
6,
2 | FULL_ON_OFF,
8,
4 | FULL_ON_OFF,
],
),
];
let mut pwm = new(&trans);
let values = [
ChannelOnOffControl {
on: 0x101,
off: 0x303,
..Default::default()
},
ChannelOnOffControl {
on: 0x102,
off: 0x304,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x103,
off: 0x305,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x104,
off: 0x306,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x105,
off: 0x307,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x106,
off: 0x308,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x107,
off: 0x309,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x108,
off: 0x400,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x109,
off: 0x401,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x200,
off: 0x402,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x201,
off: 0x403,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x202,
off: 0x404,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x203,
off: 0x405,
full_on: false,
full_off: false,
},
ChannelOnOffControl {
on: 0x204,
off: 0x406,
full_on: false,
full_off: true,
},
ChannelOnOffControl {
on: 0x205,
off: 0x407,
full_on: true,
full_off: false,
},
ChannelOnOffControl {
on: 0x206,
off: 0x408,
full_on: true,
full_off: true,
},
];
pwm.set_all_channels(&values).unwrap();
destroy(pwm);
}