eldruin / pwm-pca9685-rs Goto Github PK
View Code? Open in Web Editor NEWPlatform-agnostic Rust driver for the PCA9685 I2C 16-channel, 12-bit PWM/Servo/LED controller
License: Apache License 2.0
Platform-agnostic Rust driver for the PCA9685 I2C 16-channel, 12-bit PWM/Servo/LED controller
License: Apache License 2.0
Hey,
I don't know why but I can't get my servos to work with this library.
Leds are working fine but my servos won't move. I tried everything I can imagine to make it work but I can't and to verify my components are fine I used an esp8266 + arduino with which everything works.
Here is the last iteration of code I tried which is as similar as possible to the arduino code that works.
#![deny(unsafe_code)]
#![no_std]
#![no_main]
use cortex_m_rt::entry;
use panic_semihosting as _;
use pwm_pca9685::{Channel, Pca9685, SlaveAddr};
use stm32f1xx_hal::{
delay::Delay,
i2c::{BlockingI2c, DutyCycle, Mode},
pac,
prelude::*,
};
#[entry]
fn main() -> ! {
let cp = cortex_m::Peripherals::take().unwrap();
let dp = pac::Peripherals::take().unwrap();
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();
let clocks = rcc.cfgr.freeze(&mut flash.acr);
let mut afio = dp.AFIO.constrain(&mut rcc.apb2);
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);
let scl = gpiob.pb8.into_alternate_open_drain(&mut gpiob.crh);
let sda = gpiob.pb9.into_alternate_open_drain(&mut gpiob.crh);
let mut delay = Delay::new(cp.SYST, clocks);
let i2c = BlockingI2c::i2c1(
dp.I2C1,
(scl, sda),
&mut afio.mapr,
Mode::Fast {
frequency: 400_000.hz(),
duty_cycle: DutyCycle::Ratio2to1,
},
clocks,
&mut rcc.apb1,
1000,
10,
1000,
1000,
);
let mut pwm = Pca9685::new(i2c, SlaveAddr::default());
pwm.enable().unwrap();
pwm.set_prescale(100).unwrap();
loop {
for i in 0..4095 {
pwm.set_channel_on_off(Channel::C0, 0, i % 4095).unwrap();
}
for a in 0..=180 {
pwm.set_channel_on_off(Channel::C10, 0, pulse_width(a))
.unwrap();
delay.delay_ms(20_u32);
}
delay.delay_ms(500_u32);
for a in 0..=180 {
pwm.set_channel_on_off(Channel::C10, 0, pulse_width(180 - a))
.unwrap();
delay.delay_ms(20_u32);
}
delay.delay_ms(500_u32);
}
}
fn map_to_range(x: u32, in_min: u32, in_max: u32, out_min: u32, out_max: u32) -> u32 {
return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}
fn pulse_width(angle: u32) -> u16 {
let pulse_wide: u32 = map_to_range(angle, 0, 180, 600, 2600);
let quotient: f64 = pulse_wide as f64 / 1000000.0;
let analog_value = (quotient * 60.0 * 4096.0) as u16 % 4095;
return analog_value;
}
Of course I also tried the example code but the servos don't work either. (5v are supplied through the terminal block)
Hey,
I'm trying to control a servo and some LEDs with the library but I can't get it to output anything at all. To confirm that everything is connected correctly I used a simple python script doing exactly the same and it worked. What could I be doing wrong?
Rust program that doesn't work:
use linux_embedded_hal::I2cdev;
use pwm_pca9685::{Address, Channel, Pca9685};
use std::{thread, time::Duration};
fn main() {
let dev = I2cdev::new("/dev/i2c-1").unwrap();
let address = Address::default();
let mut pwm = Pca9685::new(dev, address).unwrap();
// This corresponds to a frequency of 60 Hz.
pwm.set_prescale(100).unwrap();
loop {
println!("Setting to 150");
pwm.set_channel_on_off(Channel::C0, 0, 150).unwrap();
thread::sleep(Duration::from_secs(3));
println!("Setting to 600");
pwm.set_channel_on_off(Channel::C0, 0, 600).unwrap();
thread::sleep(Duration::from_secs(3));
}
}
Python script that works:
from __future__ import division
import time
import Adafruit_PCA9685
pwm = Adafruit_PCA9685.PCA9685()
servo_min = 150
servo_max = 600
pwm.set_pwm_freq(60)
print('Moving servo on channel 0, press Ctrl-C to quit...')
while True:
print(servo_min)
pwm.set_pwm(0, 0, servo_min)
time.sleep(3)
print(servo_max)
pwm.set_pwm(0, 0, servo_max)
time.sleep(3)
I want to use this crate in a project that does networking with embassy tools, which rely on async.
I can use it in synchronous mode with the embedded-hal traits, but that means I will be wasting cycles waiting on the I2C device while I could be handling UDP requests or WiFi pings.
I am willing to help with the support, but want to know if you want it in this crate or if I should fork it.
As already mentioned in #7 it is pretty confusing that the examples in README.md
don't call the enable
method. It would be easier for beginners to have a ready-to-copy example which works without having to change code yourself.
The code in the "full off" is using the "ON" registers instead of the "OFF" ones.
Are there any plans to update to the new stable release of embedded-hal 1.0? It might be a little tricky since a couple big changes have been made since 0.2:
embedded_hal::i2c::I2c
Thankfully, the actual write
and write_read
methods don't seem to have changed at all, aside from the address size.
Following the example usage in the README I'm getting a consistent error on pwm.set_prescale(100).unwrap()
:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: I2C(Nix(Sys(EBUSY)))'
I've got a working python example based on https://github.com/adafruit/Adafruit_Python_PCA9685/blob/master/Adafruit_PCA9685/PCA9685.py. I'm not sure where the issue lies, or how to debug. Any pointers would be great!
Since the "full off" takes precedence if set and there is no way in this crate to clear it, I think it would be reasonable if the "full on" cleared it.
Hey!
We're working on some cool home lighting stuff using, among others, multiple PCA9685s for a ton of LED strips. We're looking to reduce the number of syscalls and the overhead on the I2C bus due to small transactions, because we might actually be hitting some limits there ๐
So, I was wondering: Instead of setting ON and OFF for each channel separately, it seems to be more efficient to just set them at the same time in order to save some bandwidth? I came up with something like this:
fn write_quad_register(
&mut self,
address: u8,
value0: u16,
value1: u16,
) -> Result<(), Error<E>> {
if self.config.is_low(BitFlagMode1::AutoInc) {
let config = self.config;
self.write_mode1(config.with_high(BitFlagMode1::AutoInc))?;
}
self.i2c
.write(
self.address,
&[
address,
value0 as u8,
(value0 >> 8) as u8,
value1 as u8,
(value1 >> 8) as u8,
],
)
.map_err(Error::I2C)
}
However, I'm not too proficient in Rust (especially with macros) and don't know how to nicely integrate this with your impl_channel_match
macro. I came up with this:
macro_rules! impl_channel_match_quad {
($s:ident, $channel:expr, $value0:expr, $value1:expr, $($C:ident, $reg:ident),*) => {
match $channel {
$(
Channel::$C => $s.write_quad_register(Register::$reg, $value0, $value1),
)*
}
};
}
impl<I2C, E> Pca9685<I2C>
where
I2C: hal::blocking::i2c::Write<Error = E>,
{
...
/// 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);
}
impl_channel_match_quad!(
self, channel, on, off, C0, C0_ON_L, C1, C1_ON_L, C2, C2_ON_L, C3, C3_ON_L, C4,
C4_ON_L, C5, C5_ON_L, C6, C6_ON_L, C7, C7_ON_L, C8, C8_ON_L, C9, C9_ON_L, C10,
C10_ON_L, C11, C11_ON_L, C12, C12_ON_L, C13, C13_ON_L, C14, C14_ON_L, C15, C15_ON_L,
All, ALL_C_ON_L
)
}
...
}
I didn't open a PR yet because, as mentioned, I'm not too comfortable with Rust yet. But I'd be happy to hear your feedback and then open one!
Alternatively, or taking this one step further: Would it be possible to write all of the 64 LED output registers in one transaction? Reading through the Linux i2cdev docs gives me the feeling that 32 bytes might be the limit for one transaction (at least when using SMBus?), so this might actually become two writes instead of one.
Please excuse if I'm talking nonsense - I don't do much embedded programming, so I am a bit confused...
Hi! First all I love this library and I'm new to embedded programming, so hopefully the questions I'm asking are relevant.
I'm trying to control a stepper motor with this driver over i2c from my rasbperry pi. I'm setting the driver to 1526hz. Very roughly, this is my setup:
let dev = hal::I2cdev::new("/dev/i2c-1").unwrap();
let address = pwm_pca9685::Address::from(0x60);
let mut pwm = pwm_pca9685::Pca9685::new(dev, address).unwrap();
pwm.enable().unwrap();
pwm.set_prescale(3).unwrap(); // 1526 Hz
// use the pwm controller here
My stepper motor code does calculations for the step and microstepping (not important now), and then calls:
self.pwm.set_channel_on_off(self.pwma, 0, CURVE[aidx] * 16).unwrap();
self.pwm.set_channel_on_off(self.pwmb, 0, CURVE[bidx] * 16).unwrap();
self.pwm.set_channel_on_off(self.ain2, 0, power & 0b0001 * 0x0FFF).unwrap();
self.pwm.set_channel_on_off(self.bin1, 0, power & 0b0010 * 0x0FFF).unwrap();
self.pwm.set_channel_on_off(self.ain1, 0, power & 0b0100 * 0x0FFF).unwrap();
self.pwm.set_channel_on_off(self.bin2, 0, power & 0b1000 * 0x0FFF).unwrap();
The important part here is that I am setting set_channel_on_off
6 times per step. When I time my application it takes approximately 600 micoseconds per call to set_channel_on_off
. If I just use set_channel_off
it takes 400 microseconds (improvement). I looked at set_all_on_off
hoping batching would work, but that takes longer than each call sequentially.
I'm trying to run my motor at faster speeds, and I'm currently blocked because each of these calls is (relatively) slow. I'm trying to figure out what to do. Do you have any idea what the latency would be here? I suspect this is either in how quicly I2C reads the value or perhaps because I'm using dev fs implementation of I2C, but researching these topics is coming up pretty empty. I'm not sure if setting the i2c baud rate would improve this or not, so wanted to ask here for ideas
Additional information: I'm running on raspberry pi with isolcpus
and taskset
, built in release mode so I shouldn't be having any problems with OS scheduling or code performance. I'm calculating the timing as the average of several thousand invocations of my step code, so the latency isn't from the timing.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.