Arm's Control

April 29, 2017
robotics embedded device-tree pwm i2c scripting

This is the robot. Ignore the mess behind it.

photo of robot with disorganized office in background
It's clamped to the desk with two red clamps because those tiny servos have some decent power

It’s a 6 degree-of-freedom arm, which means it has six hobby servos and can rotate in six independent directions. Five of them rotate the arm and the sixth controls the gripper.

A hobby servo is a small DC motor with a built-in closed-loop feedback circuit, so the motor can determine whether it’s actually rotated to where it was supposed to or not. Each motor has three wires, two for power and one for control. The signal on the control wire is a Pulse Width Modulation (PWM) signal, which means you send a square wave with a fixed period (20 ms in this case) but vary the duty cycle to tell the motor where to rotate to. These numbers are examples. The actual duty cycles used are in the range of 5 - 10%.

A few example waveforms showing pulse width modulation

LEDs are often controlled with PWM signals since you can control the brightness by varying the duty cycle. LED driver boards that can create multiple channels of PWM signals are often repurposed to drive servos instead.  This is what I’m using; it’s a pre-assembled board based around the PCA9685 chip, which can generate 16 independent PWM signals.

PCA9685 LED driver dev board from arduinofun
Don't bend pins like I'm doing in this photo

The six pins down the left are the inputs for power and for the communication lines that are used to configure the chip. Along the bottom are sixteen servo connections in banks of four; each servo connection has three wires (Brown/Red/Orange).

This board supports 16 outputs but I’m only using six; one per servo. There are three input pins for power and two for communication. The power to run the servos and the power to run the PWM chip itself is all coming from an old ATX computer power supply.

There are two different rails because the PWM chip itself is running off 3.3V but the servo motors need at least 5V. Luckily ATX power supplies have both of these. The 3.3V is fed to the PWM chip, and the 5V is passed on to the positive power connection for all sixteen servo channels.

To make connections more easily to the ATX power supply, I’m using a breakout board. There’s really not much on this board; it just connects the power rails from the ATX connector (meant for a motherboard) to the holes so they’re easier to connect to. I had to solder pins in for GND, 3.3V and 5V so I could connect them to the PWM driver board with jumper wires. The breakout also has some status LEDs and a power switch. If the robot goes berserk you can kill the power to the whole system with this switch.

Photo of ATX breakout board to obtain 5V and 12V from PC PSU
Use an old PC ATX power supply for electronics projects - no shortage of watts!

The PWM chip can generate 16 PWM signals but it needs to be told what period and duty cycle to use for each of them. The servo motors need a period of 20 ms and a duty cycle around 5-15%. The control for the PWM chip comes from a Raspberry Pi using the I2C protocol. The blue and green wires are clock and data, respectively. The raspberry pi is a very inexpensive single-board-computer (SBC) with a main processor made by Broadcom. It’s running a full Linux system, which is installed on the black SD card on the right edge of the image. It’s powered by a regular micro USB cable (5V) like the kind used to charge a cell phone.

Raspberry pi showing UART and I2C connections
Blue and green for I2C, Orange/Yellow/Black for RX/TX/GND of UART

I2C is a low level serial protocol, which means individual bits are sent down the wire one at a time at around 400 kilobits per second. I2C uses a clock line (a clock is just a square wave at a constant 50% duty cycle) which is required so the receiving system (the PWM chip) can look at the data line and know when one bit ends and the next one begins. Bits are received one by one and reconstructed into the configuration instructions for the PWM chip. You can see the I2C lines (blue and green) connected between the PWM driver board and the black GPIO pin header along the bottom right edge.

The other wires connected to the GPIO bank are for a UART serial connection to my PC. My computer is too new to have a physical serial port so I’m using a USB to serial bridge. The three wires are TX, RX and ground. This serial connection isn’t strictly necessary, since the pi has a network connection (black cable on the left of the photo), but it can come in handy when something goes wrong early in the boot process (ie. kernel panic), before the network has been set up.

The pi is running Raspbian, which is a raspberry-flavored version of Debian Linux. Linux uses something called Device Tree to describe the hardware that is connected to the system. When Linux boots, it reads the list of hardware from the device tree and tries to find compatible driver software for every piece of hardware.

To add support for new hardware, you need to write a description of the hardware (eg. which pins it’s connected to, what signalling protocol it understands) and you need driver software that can configure the hardware and facilitate communication between the hardware and the generic interfaces that the rest of the operating system expects.

Luckily there is already driver software and configuration for this particular PWM chip in Linux; it just needs to be turned on. I enabled I2C support by adding these lines to /boot/config.txt with the vim editor.

dtparam=i2c\_arm=on dtoverlay=i2c-pwm-pca9685a

The system may still try to load the i2c_dev driver, which is a generic i2c driver that I don’t want. If lsmod shows that i2c_dev is loaded, you can edit /etc/modules and remove the line that says to load i2c_dev.

Once these changes are made, you need to reboot. After rebooting, the driver should be loaded. You can verify this with lsmod and you should see something like this:

Module Size Used by pwm\_pca9685 3856 6

Once the driver is loaded successfully, it will create virtual files in the sysfs to let you control the hardware. These sysfs nodes look like files but when you read from or write to them, instead of accessing a normal file on a disk, they will access the hardware and do things like turning on PWM channels and setting the duty cycle. This is an example of a design philosophy in Linux sometimes described as “everything is a file”. To see the sysfs nodes that control the PWM hardware, you should also be able to run ls /sys/class/pwm and see something like this:

root@raspberrypi:/home/pi# cd /sys/class/pwm
root@raspberrypi:/sys/class/pwm# ls pwmchip0
root@raspberrypi:/sys/class/pwm# cd pwmchip0 
root@raspberrypi:/sys/class/pwm/pwmchip0# ls
device export npwm power subsystem uevent unexport

These are all sysfs nodes that the driver has created to control the PWM chip. You can ignore the power/subsystem/uevent nodes; these are generic nodes created for any hardware that has sysfs nodes created for it.

To control any of the 16 individual channels, I “export” the channel to user space by echoing the number of the channel I want into the export node. I do this once for each of the six channels. The 16 channels are numbered 0 through 15.  Note that the > sign is redirection, read as “into” and does not mean “greater than”.

root@raspberrypi:/sys/class/pwm/pwmchip0# echo 0 > export
root@raspberrypi:/sys/class/pwm/pwmchip0# ls
device export npwm power pwm0 subsystem uevent unexport

This tells the driver to get ready to configure that particular PWM channel. The nodes look almost the same but there is one new node called pwm0. It then creates some new sysfs nodes specific to this channel for controlling it.

root@raspberrypi:/sys/class/pwm/pwmchip0# cd pwm0 
root@raspberrypi:/sys/class/pwm/pwmchip0/pwm0# ls 
duty_cycle enable period polarity power uevent

These nodes correspond to the duty cycle and period of the first PWM channel, which are the parameters that will eventually control the servo motor. The kernel driver provides these nodes that can accept values (specified in nanoseconds) and it will convert them to the format the PWM chip understands and send them over the I2C bus (blue and green wires). The PWM chip will (hopefully) receive and understand these commands and turn on the actual PWM output signal on channel 0, which will in turn make the motors turn.

The entire system can be controlled by typing commands that write text into these nodes. For example, to open and close the gripper (which I have connected to PWM channel 7) I can send the commands like this:

root@raspberrypi:/sys/class/pwm/pwmchip0# echo 7 > export 
root@raspberrypi:/sys/class/pwm/pwmchip0# ls device export npwm power pwm0 pwm7 subsystem uevent unexport
root@raspberrypi:/sys/class/pwm/pwmchip0# cd pwm7
root@raspberrypi:/sys/class/pwm/pwmchip0/pwm7# ls
duty_cycle enable period polarity power uevent
root@raspberrypi:/sys/class/pwm/pwmchip0/pwm7# echo 20000000 > period
root@raspberrypi:/sys/class/pwm/pwmchip0/pwm7# echo 1200000 > duty_cycle # the claw opens
root@raspberrypi:/sys/class/pwm/pwmchip0/pwm7# echo 1900000 > duty_cycle # the claw shuts

I played around with these for a little while by hand but eventually it got tedious so I wrote some scripts to make it easier. These scripts just run a series of commands sequentially so the robot is still only able to move one joint at a time. This bash script has some predefined values and functions that allow me to move the robot with less typing. For example, to open and close the gripper I could also use:

#!/bin/bash
SYSFOLDER=/sys/class/pwm/pwmchip0/
CLAW_ID=7
CLAW_OPEN=1200000
CLAW_CLOSED=1900000
PERIOD=20000000

function enable() {
echo $1 > $SYSFOLDER/export
echo $PERIOD > $SYSFOLDER/pwm$1/period
}

function claw_open() {
echo $CLAW_OPEN > $SYSFOLDER/pwm
$CLAW_ID/duty_cycle
} 

function claw_close() {
echo $CLAW_CLOSED > $SYSFOLDER/pwm
$CLAW_ID/duty_cycle
}

Then you can save yourself some typing and just run:

root@raspberrypi:~# enable $CLAW_ID
root@raspberrypi:~# claw_open
root@raspberrypi:~# claw_close

Once you have this working you can build up some complicated sequences of movements.

My next goals are to make the motion smoother by interpolating values instead of jumping from one position to another, and to work on getting more than one motor moving simultaneously.

Update 2018-11-05

It’s time for my yearly robot update! I haven’t really worked on this for a while but I decided to get it going again recently and I have some code to share.

I decided that bash scripts echoing strings into sysfs was kind of a kludgy way to talk to my PWM controller so I hacked together a device tree overlay, a kernel driver and a basic user space application (more of a proof of concept really).  My kernel driver leverages the the pca9685 driver included in the kernel, and my device tree overlay  actually includes the source that is compiled into the i2c-pwm-pca9685a.dtbo that the raspberry pi uses (I haven’t figured out if it’s possible to attach my device tree nodes to the pca9685 overlay so I’ve just replicated it).

The kernel driver initializes some memory, and then creates an entry called /dev/robot that can be accessed through a user space call to ioctl(). When the device tree is loaded, the kernel module gets probed six times, once for each servo. The driver stores the info needed to find that PWM device again. It handles requests that come in via ioctl() and translates them to the kernel’s PWM interface.

The user space application attempts to do some fancy interpolation but really it’s just a proof of concept. The next thing I plan to do is re-write the user space code as a loadable library with a higher level API, and then maybe try to do some fancy algorithmic stuff on top of that.

https://github.com/tabell/arm-control