This is a tiny sample application demonstrating the usage of the pwm_pca9685
crate. I am new to Rust and somewhat new to embedded programming so bear with me. I am planning to use the knowledge gained here in a robotics project I am planning to do in my free time. Shoot me a message if you have feedback.
This should be a fully working example. Hopefully someone can find this useful and can be a complementer to the original introductory blog post by eldruin
: https://blog.eldruin.com/pca9685-pwm-led-servo-controller-driver-in-rust/
You should be able just clone it and cargo run
. Only tested on 32bit Raspbian though.
I believe it won't work on Windows WSL because of the linux_embedded_hal
dependency but you will want a board with IO pins anyway.
- SunFounder SF3218MG servos. I believe they are Chinese company, they might not have sales globally. About $16 each. The documentation is not great.
- The PCA9685 board is manufactured by VKLSVAN. Also a Chinese company, they don't seem to have online presence. About $4.5.
- The compute unit is a Raspberry Pi3B that I had lying around.
See the picture below showing my moderately messy setup. One could just directly connect the RPi to PCA9685 but I need to order a female-female pin cable for that :)
There are 2 servos, the code will control them from the 2 extremes back and forth and then step through the range (they are going to step in the opposite direction). My servos have 270deg angle range. The 2 channels are defined in 2 const
s at the top of the code: LEFT
and RIGHT
. No particular reason why #0
and #12
apart from I am planning to use 12 servos in the end. You can change it easily. I assume the example should be enough to give you an idea to modify it to more or less # of servos.
If you want both servos to do the same thing, it is cleaner to use set_all_on_off()
instead of set_channel_on_off()
.
servo_steps
is kept as a floating point number so that we don't accumulate rounding error in the loop (it has to be an integer though when passed to the pwm
module).
Other PCA9685 sample codes you find on the internet (Python, C++, Rust, whatever) will use different values and the value is dependent on your servo. Originally I just experimented with trial and error to find the boundaries but if I am not mistaken, the calulation is as follows (again, I am new to this):
- The control frequency is 60Hz (this is a default for all servos, my servo does not seem to specify it)
- 60Hz gives us 1/60s (=16,666.666... usec) pulses/periods in the carrier signal
- The data sheet of my servo specifies:
Pulse Width Range: 500 ~ 2500 usec
andNeutral Position: 1500 usec
- According to my understanding the PWM signal for a servo always begins at the beggining of the cycle (0th pulse in the carrier signal)
PCA9685
has 12-bit resolution = 4096 steps- So this gives us the calculation for the two extrema:
- min:
(500 / (1 / 60 * 1000000)) * 4096 = 0.03 * 4096 = 122.88
- max:
(2500 / (1 / 60 * 1000000)) * 4096 = 0.15 * 4096 = 614.4
- min:
- In other words:
- min: 3% duty cycle (the first 3% of the carrier signal is
ON
, the remaining 97% isOFF
) - max: 15% duty cycle (the first 15% of the carrier signal is
ON
, the remaining 85% isOFF
)
- min: 3% duty cycle (the first 3% of the carrier signal is
The values for your servo are likely different. The ON
period doesn't necessarily need to start at the beggining of the signal but it does in this case.
All false
is the default address, you can substitute it to the crate's predefined value. All false means none of the address headers/jumpers are soldered. See more details for example here: https://learn.adafruit.com/16-channel-pwm-servo-driver?view=all#addressing-the-boards-848847-3
I might add tiny things but I will keep this as a minimal working example and start a new codebase if I move to the next step with my robotics project.
One logical further next small step would be to switch to angles instead of a "value" that is defined by the duty cycle. In the example above is 0° = 122.88 and 270° = 614.4 (plus/minus error which is not small). It's a simple calculation. Again, these values are different for different servo motors.