Skip to content

Commit

Permalink
Move sync code to the blocking module
Browse files Browse the repository at this point in the history
Move sync code that interacts with the I2C to a separate module,
in preparation for adding an async module.
  • Loading branch information
nils-van-zuijlen authored and eldruin committed Jan 24, 2024
1 parent a6fb6f7 commit 195be3c
Show file tree
Hide file tree
Showing 8 changed files with 458 additions and 442 deletions.
97 changes: 97 additions & 0 deletions src/blocking/channels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use crate::{hal, Channel, Error, Pca9685, Register, channels::{get_register_on, get_register_off}};

impl<I2C, E> Pca9685<I2C>
where
I2C: hal::blocking::i2c::Write<Error = E> + hal::blocking::i2c::WriteRead<Error = E>,
{
/// Set the `ON` counter for the selected channel.
///
/// Note that the full off setting takes precedence over the `on` settings.
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub fn set_channel_on(&mut self, channel: Channel, value: u16) -> Result<(), Error<E>> {
if value > 4095 {
return Err(Error::InvalidInputData);
}
let reg = get_register_on(channel);
self.write_double_register(reg, value)
}

/// Set the `OFF` counter for the selected channel.
pub fn set_channel_off(&mut self, channel: Channel, value: u16) -> Result<(), Error<E>> {
if value > 4095 {
return Err(Error::InvalidInputData);
}
let reg = get_register_off(channel);
self.write_double_register(reg, value)
}

/// Set the `ON` and `OFF` counters for the selected channel.
///
/// Note that the full off setting takes precedence over the `on` settings.
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub fn set_channel_on_off(
&mut self,
channel: Channel,
on: u16,
off: u16,
) -> Result<(), Error<E>> {
if on > 4095 || off > 4095 {
return Err(Error::InvalidInputData);
}
let reg = get_register_on(channel);
self.write_two_double_registers(reg, on, off)
}

/// Set the channel always on.
///
/// The turning on is delayed by the value argument.
/// Note that the full off setting takes precedence over the `on` settings.
///
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub fn set_channel_full_on(&mut self, channel: Channel, value: u16) -> Result<(), Error<E>> {
if value > 4095 {
return Err(Error::InvalidInputData);
}
let reg = get_register_on(channel);
let value = value | 0b0001_0000_0000_0000;
self.write_double_register(reg, value)
}

/// Set the channel always off.
///
/// This takes precedence over the `on` settings and can be cleared by setting
/// the `off` counter with [`set_channel_off`](struct.Pca9685.html#method.set_channel_off).
///
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub fn set_channel_full_off(&mut self, channel: Channel) -> Result<(), Error<E>> {
let reg = get_register_off(channel);
let value = 0b0001_0000_0000_0000;
self.write_double_register(reg, value)
}

/// Set the `ON` and `OFF` counter for each channel at once.
///
/// The index of the value in the arrays corresponds to the channel: 0-15.
/// Note that the full off setting takes precedence over the `on` settings.
/// See section 7.3.3 "LED output and PWM control" of the datasheet for
/// further details.
pub fn set_all_on_off(&mut self, on: &[u16; 16], off: &[u16; 16]) -> Result<(), Error<E>> {
let mut data = [0; 65];
data[0] = Register::C0_ON_L;
for (i, (on, off)) in on.iter().zip(off).enumerate() {
if *on > 4095 || *off > 4095 {
return Err(Error::InvalidInputData);
}
data[i * 4 + 1] = *on as u8;
data[i * 4 + 2] = (*on >> 8) as u8;
data[i * 4 + 3] = *off as u8;
data[i * 4 + 4] = (*off >> 8) as u8;
}
self.enable_auto_increment()?;
self.i2c.write(self.address, &data).map_err(Error::I2C)
}
}
272 changes: 272 additions & 0 deletions src/blocking/device_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
use crate::{
config::{BitFlagMode1, BitFlagMode2, Config},
Pca9685, ProgrammableAddress,
hal::blocking::{i2c, delay::DelayUs},
Address, DisabledOutputValue, Error, OutputDriver, OutputLogicState, OutputStateChange,
Register,
};


impl<I2C, E> Pca9685<I2C>
where
I2C: i2c::Write<Error = E> + i2c::WriteRead<Error = E>,
{
/// Create a new instance of the device.
pub fn new<A: Into<Address>>(i2c: I2C, address: A) -> Result<Self, Error<E>> {
let a = address.into();

Self::check_address(a.0)?;

Ok(Pca9685 {
i2c,
address: a.0,
config: Config::default(),
})
}

/// Destroy driver instance, return I²C bus instance.
pub fn destroy(self) -> I2C {
self.i2c
}

/// Enable the controller.
pub fn enable(&mut self) -> Result<(), Error<E>> {
let config = self.config;
self.write_mode1(config.with_low(BitFlagMode1::Sleep))
}

/// Disable the controller (sleep).
pub fn disable(&mut self) -> Result<(), Error<E>> {
let config = self.config;
self.write_mode1(config.with_high(BitFlagMode1::Sleep))
}

/// Put the controller to sleep while keeping the PWM register
/// contents in preparation for a future restart.
pub fn enable_restart_and_disable(&mut self) -> Result<(), Error<E>> {
let config = self.config.with_high(BitFlagMode1::Sleep);
self.write_mode1(config.with_high(BitFlagMode1::Restart))?;
// Do not store restart bit high as writing this bit high again
// would internally clear it to 0. Writing 0 has no effect.
self.config = config;
Ok(())
}

/// Re-enable the controller after a sleep with restart enabled so that
/// previously active PWM channels are restarted.
///
/// This includes a delay of 500us in order for the oscillator to stabilize.
/// If you cannot afford a 500us delay you can use `restart_nonblocking()`.
pub fn restart(&mut self, delay: &mut impl DelayUs<u16>) -> Result<(), Error<E>> {
let mode1 = self.read_register(Register::MODE1)?;
if (mode1 & BitFlagMode1::Restart as u8) != 0 {
self.enable()?;
delay.delay_us(500_u16);
let previous = self.config;
let config = previous.with_high(BitFlagMode1::Restart);
self.write_mode1(config)?;
self.config = previous;
}
Ok(())
}

/// Re-enable the controller after a sleep with restart enabled so that
/// previously active PWM channels are restarted (non-blocking version).
///
/// This is a nonblocking version where you are responsible for waiting at
/// least 500us after the receiving the first `WouldBlock` error before
/// calling again to continue.
pub fn restart_nonblocking(&mut self) -> nb::Result<(), Error<E>> {
let mode1 = self
.read_register(Register::MODE1)
.map_err(nb::Error::Other)?;
let restart_high = (mode1 & BitFlagMode1::Restart as u8) != 0;
let sleep_high = (mode1 & BitFlagMode1::Sleep as u8) != 0;
if restart_high {
if sleep_high {
self.enable().map_err(nb::Error::Other)?;
return Err(nb::Error::WouldBlock);
} else {
let previous = self.config;
let config = previous.with_high(BitFlagMode1::Restart);
self.write_mode1(config).map_err(nb::Error::Other)?;
self.config = previous;
}
}
Ok(())
}

/// Set one of the programmable addresses.
///
/// Initially these are not enabled. Once you set this, you can call
/// `enable_programmable_address()` and then use `set_address()` to configure
/// the driver to use the new address.
pub fn set_programmable_address<A: Into<Address>>(
&mut self,
address_type: ProgrammableAddress,
address: A,
) -> Result<(), Error<E>> {
let a = address.into();

Self::check_address(a.0)?;
let reg = match address_type {
ProgrammableAddress::Subaddress1 => Register::SUBADDR1,
ProgrammableAddress::Subaddress2 => Register::SUBADDR2,
ProgrammableAddress::Subaddress3 => Register::SUBADDR3,
ProgrammableAddress::AllCall => Register::ALL_CALL_ADDR,
};
self.i2c
.write(self.address, &[reg, a.0])
.map_err(Error::I2C)
}

/// Enable responding to programmable address
pub fn enable_programmable_address(
&mut self,
address_type: ProgrammableAddress,
) -> Result<(), Error<E>> {
let flag = Self::get_subaddr_bitflag(address_type);
let config = self.config;
self.write_mode1(config.with_high(flag))
}

/// Disable responding to programmable address
pub fn disable_programmable_address(
&mut self,
address_type: ProgrammableAddress,
) -> Result<(), Error<E>> {
let flag = Self::get_subaddr_bitflag(address_type);
let config = self.config;
self.write_mode1(config.with_low(flag))
}

/// Sets the address used by the driver for communication.
///
/// This does not have any effect on the hardware and is useful when
/// switching between programmable addresses and the fixed hardware address
/// for communication.
pub fn set_address<A: Into<Address>>(&mut self, address: A) -> Result<(), Error<E>> {
let a = address.into();

Self::check_address(a.0)?;
self.address = a.0;

Ok(())
}

fn check_address(address: u8) -> Result<(), Error<E>> {
const LED_ALL_CALL: u8 = 0b111_0000;
// const SW_RESET: u8 = 0b000_0011; this gets absorbed by the high speed mode test
const HIGH_SPEED_MODE: u8 = 0b00_0111;
if address == 0 || address > 0x7F || address == LED_ALL_CALL || address <= HIGH_SPEED_MODE {
Err(Error::InvalidInputData)
} else {
Ok(())
}
}

/// Set the output change behavior. Either byte-by-byte or all at the same time.
///
/// Note that update on ACK requires all 4 PWM channel registers to be loaded before
/// outputs are changed on the last ACK.
pub fn set_output_change_behavior(
&mut self,
change_behavior: OutputStateChange,
) -> Result<(), Error<E>> {
let config = match change_behavior {
OutputStateChange::OnStop => self.config.with_low(BitFlagMode2::Och),
OutputStateChange::OnAck => self.config.with_high(BitFlagMode2::Och),
};
self.write_mode2(config)
}

/// Set the output driver configuration.
pub fn set_output_driver(&mut self, driver: OutputDriver) -> Result<(), Error<E>> {
let config = match driver {
OutputDriver::TotemPole => self.config.with_high(BitFlagMode2::OutDrv),
OutputDriver::OpenDrain => self.config.with_low(BitFlagMode2::OutDrv),
};
self.write_mode2(config)
}

/// Set the output value when outputs are disabled (`OE` = 1).
pub fn set_disabled_output_value(
&mut self,
value: DisabledOutputValue,
) -> Result<(), Error<E>> {
let config = match value {
DisabledOutputValue::Zero => self
.config
.with_low(BitFlagMode2::OutNe0)
.with_low(BitFlagMode2::OutNe1),
DisabledOutputValue::OutputDriver => self
.config
.with_high(BitFlagMode2::OutNe0)
.with_low(BitFlagMode2::OutNe1),
DisabledOutputValue::HighImpedance => self
.config
.with_low(BitFlagMode2::OutNe0)
.with_high(BitFlagMode2::OutNe1),
};
self.write_mode2(config)
}

/// Set the output logic state
///
/// This allows for inversion of the output logic. Applicable when `OE = 0`.
pub fn set_output_logic_state(&mut self, state: OutputLogicState) -> Result<(), Error<E>> {
let config = self.config;
match state {
OutputLogicState::Direct => self.write_mode2(config.with_low(BitFlagMode2::Invrt)),
OutputLogicState::Inverted => self.write_mode2(config.with_high(BitFlagMode2::Invrt)),
}
}

/// Enable using the EXTCLK pin as clock source input.
///
/// This setting is _sticky_. It can only be cleared by a power cycle or
/// a software reset.
pub fn use_external_clock(&mut self) -> Result<(), Error<E>> {
let config = self.config;
self.write_mode1(config.with_high(BitFlagMode1::Sleep))?;
let config = self.config;
self.write_mode1(config.with_high(BitFlagMode1::ExtClk))
}

/// Set the prescale value.
///
/// The prescale value can be calculated for an update rate with the formula:
/// `prescale_value = round(osc_value / (4096 * update_rate)) - 1`
///
/// The minimum prescale value is 3, which corresonds to an update rate of
/// 1526 Hz. The maximum prescale value is 255, which corresponds to an
/// update rate of 24 Hz.
///
/// If you want to control a servo, set a prescale value of 100. This will
/// correspond to a frequency of about 60 Hz, which is the frequency at
/// which servos work.
///
/// Internally this function stops the oscillator and restarts it after
/// setting the prescale value if it was running.
pub fn set_prescale(&mut self, prescale: u8) -> Result<(), Error<E>> {
if prescale < 3 {
return Err(Error::InvalidInputData);
}
let config = self.config;
let was_oscillator_running = config.is_low(BitFlagMode1::Sleep);
if was_oscillator_running {
// stop the oscillator
self.write_mode1(config.with_high(BitFlagMode1::Sleep))?;
}

self.i2c
.write(self.address, &[Register::PRE_SCALE, prescale])
.map_err(Error::I2C)?;

if was_oscillator_running {
// restart the oscillator
self.write_mode1(config)?;
}
Ok(())
}
}
3 changes: 3 additions & 0 deletions src/blocking/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod device_impl;
mod register_access;
mod channels;
Loading

0 comments on commit 195be3c

Please sign in to comment.