New PCA9685 driver for ROS

I wrote a driver for the PCA9685 I2C PWM driver chip. This chip is intended to be used with LED applications but is also used in a number of servo and motor controller boards, including a few Adafruit and Waveshare boards among others.

A couple of notable features I added that I haven’t seen done in other similar packages:

  • The ability to update only certain channels in a ROS command, and not update all of them. This allows multiple ROS nodes to publish to the same command topic but take charge of different subsets of the 16 channels.
  • A timeout. If a channel isn’t updated within that time, it gets set to 0. This is useful to avoid servos burning out if your control logic crashes, and also as a heartbeat timeout in the case of motor controllers.
3 Likes

Hi dheera,
Thanks for sharing your package. I am new to ROS so I’m still confusing after reading your readme file. You’re using Int32MultiArray as the message type, but in your description, the subscriber take 16 values for each channel. How are those two related. If I want to write send command to this node, how do I define the values of label, size, stride, data_offset, and data respectively to specify the channel?
Thanks much,
Zhouqiao

Hi Zhouqiao,
The code currently only reads the data field and expects 16 values. It doesn’t actually read label/stride/data_offset, so it’ll work without setting those, but it’s probably most “correct” to just define it as a one-dimensional array of 16 values. i.e.

dim[0].label = “command”
dim[0].size = 16
dim[0].stride = 16

Thanks, works perfect.

Hi Dheera,
I have not been able to get this to work.
I have the Adafruit servo board working outside of ROS but I need to make it work within ROS as this project does.
I am fairly new to ROS but I do have several nodes working.
I work in python.
Is there something particular to get a cpp script working?

The approach I took was to download your project in a catkin workplace and compile.

If you could guide me on how to install your files in an existing project, that would be awsome.
Thanks.

  • Did you get a compile or run error, and what is the error?
  • What hardware do you have the servo board connected to?
  • Are you able to see the servo board using i2cdetect?

Marc Boudreau via ROS ros@discoursemail.com 於 2019年12月19日 週四 上午8:42寫道:

Hi,

Thanks for replying, I appreciate it

Like I said, I am new to cpp nodes. I did not realize that you do not place the file extension “.cpp” as you need to place the “.py” when using python.

I now have the node running!

However, I have yet to figure out the publish nomenclature.

I have tried variations of:

rostopic pub Int32MultiArray/command {data:[32767,32767,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]}

but always get errors:

Usage: rostopic pub /topic type [args…]

rostopic: error: no such option: -]

Le jeu. 19 déc. 2019 à 17:08, Dheera Venkatraman via ROS ros@discoursemail.com a écrit :

it works!
This makes me happy!

Thanks for sharing your work!

Le jeu. 19 déc. 2019 à 17:08, Dheera Venkatraman via ROS ros@discoursemail.com a écrit :

Hello Dheera,
Thank you for you package, I am having a similar issue as the rest of the folks on this list. i tried to pub to i2c 0 like the following
rostopic pub /pca9685_node/command std_msgs/Int32MultiArray “{data: [32767, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]}”
I publishing and latching message. Press ctrl-C to terminate
and nothing happens at this point. i am able to move my servos using the PCA class and Servokit otherwise.

Help please

Hi @k3street

  • Servos use PWM values between approximately 2800 to 7600 to describe a full range of motion. Issuing PWM values outside this range (e.g. 0 or 32767) will cause the servo to detach. The [32767, 0, …] example is for motor driver chips and LEDs, not servos.
  • Most hobby servos expect a PWM frequency of 50Hz so set the ROS “frequency” parameter to 50

Hope this helps! Let me know if it still doesn’t work.

1 Like

Thanks for the quick response, let me clarify what I am experiencing. I am running metal gear hobby servos. When I initiate rosrun the servos move to the 0 position and stay there. No error is presented in info.

nh_priv.param("device", param_device, (std::string)"/dev/i2c-0");
nh_priv.param("address", param_address, (int)PCA9685_ADDRESS);
nh_priv.param("frequency", param_frequency, (int)50);
nh_priv.param("frame_id", param_frame_id, (std::string)"imu");

When I enter the command:
rostopic pub /pca9685_node/command std_msgs/Int32MultiArray “{data: [4000, 4000, 4000, 4000, 4000, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1]}”

As I have 5 servos I get the publishing and latching message indefinitely.

Thank you again for helping me troubleshoot.

A. Can you do

sudo i2cdetect -y 0

B. What OS/hardware are you on and have you double checked you wired it correctly?

C. When you send the 4000 command, what happens? Do they stay at the 0 position?

By the way I think there was a bug in the negative timeout logic – not documented yet but I recently added a feature where if you use a negative value for timeout, it would turn off the servo if the servo value doesn’t change for a set period of time (as opposed to not receiving a command). This was so it is better possible to support JointTrajectoryController interfaces without burning out the servos.

Although the bug should not affect positive timeouts you might want to run a git pull again just in case.

a. I see you are definitely on to something, -y returns nothing i2cdetect -y -r 0 returns 40 and 70 at 0.

b. I am able to manipulate servos using servokit and python3. I am making sure my connection using ServoKit is disposed. I am using a Jetson Nano, pCA9685 and MG995 servos

c. when i send the 4000 command nothing happens beyond the standard latching ros response with the option to quit CTRL C

  • 40 is good, that means it is seeing the board
  • If you’re on a Jetson Nano, make sure your user is in the i2c group, e.g. sudo usermod -aG i2c nvidia and reboot
  • Do you have an oscilloscope by any chance and can you see if there is any PWM output at all, even briefly, when you issue the command?
  • Make sure you pull the latest version from the repo

Great code dheera thanks for sharing!

I just found a bug that you might want to sort out: When giving the same input periodically in a time interval less than the timeout it still resets the values. There is an easy way of avoiding this by increasing and decreasing in 1 each time so the input is not the same (the result is pretty much the same). Just in case you want to look at it!

Thanks and good work,

Dudu014

@dheera Thanks a lot for this driver it really saved me i use jetson nano and arduino due for my project mobile manipulator https://github.com/panagelak/Open_Mobile_Manipulator

and i had connect the pca9685 to arduino with rosserial and a driver but this driver had erratic behaviour with your driver i connected the pca9685 directly to jetson nano and it works!!!

In order to control the arm with ros control and moveit i had wrote a hardware interface that ( because no feedback on the servos) (on the read() function i pass directly the commands on the write() function) this has the effect that the robot-rviz thinks the arm is moving whether or not you have connected the real arm

Then i made a node that subscribes to the joint_states transforms them into appropiate servo commands and publishes the array messages for your driver this node can be found here https://github.com/panagelak/Open_Mobile_Manipulator/blob/master/ommp_bringup/src/pub_to_arduino_class.cpp

  1. as @Dudu014 said when the arm sit still and recieve the same joint_states - array message after the timeout it depowers the servos. This was annoying but i made a workaround if same joint_states publish +1 -1 values. Maybe a fix for that?

  2. It seems that i could push the servos in ranges greater than 180 degrees e.g i get the full range of motion -90 - +90 while my servos are not perfectly aligned in the center. previously i had to sacrifice some range. This is just confusing to me but it doesnt seem to destroy the servos

  3. I had try to use the ServoKit python3 library and it worked but i couldnt connect it with ros because of the python 2 interperter of ros, so your driver saved me!!

Thanks for your driver really helpfull!!!

1 Like

Hmm I’ll try to track down this bug! The +1/-1 seems like a workaround but it shouldn’t normally operate that way.

My latest commit’s intended behavior is for a positive timeout to timeout if no command is issued, and a negative timeout to timeout if no change happens.

I also did some interfacing with the joint_trajectory_controller with my Luxo robot:


which isn’t quite well documented but there is some code in there :wink: I’ll take a look at this again.

Hey dheera,

i appreciate your work and i wanted to try out that node you created.
My Hardware is a RasPi 3B+ and i got the Motor HAT on top of it, with 4 servo motors connected.
Until now i used a simple c program to run them, but now i want to try running it via ros.

In the stacking hats instruction they say the base i2c adress of each board is 0x60.
https://learn.adafruit.com/adafruit-dc-and-stepper-motor-hat-for-raspberry-pi/stacking-hats
Now in your documentation you say that the 0x60 adress is the default address.

When i try to publish to the Topic with that command Nothing happens:

rostopic pub /pca9685_node/command std_msgs/Int32MultiArray “ {data: [-1, -1, -1, -1, -1, -1, -1, -1, 32767, 65535, 0, -1, -1, -1, -1, -1]}”

Before i worked with the 0x60 address too and everything worked fine. When i check the i2c Connection with i2cdetect -y 1 he Shows me devices on 0x60 / 0x70.

Am i doing something wrong or did i miss something?

Thank you for your help.

Hi @rosstarter
0x40 is the default address for the ROS node, as is used on many boards. For the Adafruit board it the board’s default is 0x60. Make sure you are setting the ROS “address” parameter to 0x60 == 96.

Post back if this didn’t work!

1 Like