Thursday, March 19, 2015

Creating a Java Driver for the Adafruit PCA9685 PWM Driver

The hex robot is going to have a large number of servos, 3 per leg total of 18 servos. I needed a way to drive that many servos from the Raspberry Pi. I ended up buying a couple of Adafruit's rather cool little breakout boards the Adafruit 16-Channel 12-bit PWM/Servo Driver - I2C interface - PCA9685


This little board is based around a NXP Semiconductors PCA9685 chip and is supports an I2C interface. The chip does the hard work of outputting servo pwm signals that will work well with RC Style servos. It's designed for LEDs but so far it works just as well with Servos. I suspect I will have to beef up the power delivery for running all the servos.

Adafruit has some good examples on how to use this board with the Raspberry Pi check the product page for links or Adafruit's Github for the python examples.

I started working on Java driver for the board. I wanted the design of the driver to support unit testing in my application and have just finished creating the framework for the driver. As this is a work in progress the code for the driver is still part of my main project. Once I have got it more stable I'll separated it if any one expresses any interest in contributing to it. Until then it's likely to continue to evolve as I explore the functionality.

The main concern I had was using the Pi4J library for I2C communication has a dependency on Pi4J native libraries. These wouldn't build in maven for me due to the missing native libraries. If your trying to build in maven with the Pi4J library you'll probably need to exclude the native libraries unless you have them installed.
        
            com.pi4j
            pi4j-core
            ${pi4j.version}
            
                
                    com.pi4j
                    pi4j-native
                
            
        

The goal for creating the Java driver was to ensure I could run unit tests without the native libraries present.




Driver Classes



I wanted the ServoDriver interface to abstract the rest of the application code from the driver implementation. The driver provides methods for setting the angle of a servo and the frequency of refresh.
The PCA9685Device abstracts the read write operations of the device as well as providing all the register constants for the device.
The AdafruitServoDriver provides the servo driver higher level functions for providing the sequencing of reads and writes to initialize and interact with the device via a Device implementation. There are some settings that have to be set that the servo driver implementation encapsulates. For example the PRE_SCALE register defines the frequency at which the outputs modulate. The prescale value is determined with the formula shown

prescaleVal = round(oscClock/(4096 * updateRate)) - 1
Example assuming 25MHz the modulation frequency of 200 Hz
prescaleVal = round(25000000 / (4096 * 200)) - 1 = 30

In my case the frequency is 50Hz, this is fairly average frequency for RC Servos. The driver can be configured via a property. (The example of 200Hz is provided in the PCA9685 data sheet incase your wondering why. It was a good known good example to test)

The Device interface provides lower level read/write operations for interacting with the device registers. When using netbeans and running on the Raspberry Pi directly it is easier to test agains the actual board itself. However I wanted to be able to test my code in unit tests. To enable this I create a MockPCA9685Device that very simply provides a set of registers that one can use in unit tests to check the registers are being set correctly.

For example in my ServoDriverTest I initialize the servo driver as follows

    
@Before
    public void setUpServoDriver() {
        try {
            this.configuration = new PropertiesConfiguration("com.margic.pihex.properties");
        }catch(ConfigurationException ce){
            LOGGER.error("Failed to get properties.", ce);
        }
        mockDevice = new MockPCA9685Device();
        this.driver = new AdafruitServoDriver(configuration, mockDevice);
    }

You can see that the AdafruitServoDriver is constructed with the mock device. In the actual code the real device can be provided in the construct or can be injected using dependency injection.

The following test tests setting the PWM frequency. This involves setting the PRESCALE register in the PCA9685 device. Register 0xFE if your interested. For my default 50Hz this register should be set to the value 121 (0x79). This would be difficult to test in a unit test as viewing the registers is not easy. The mockdevice helps. The following test test setting the frequency to a few different values and reads the value from the device to assert the register is set correctly to the correct PRESCALE value (disclaimer: I'm stating the are the correct values, but to be honest I haven't validated them yet)

 
    @Test
    public void testSetPWMFrequency() throws IOException{
        driver.setPWMFrequency(50);
        mockDevice.dumpRegisters();
        assertEquals(121, mockDevice.readRegister(PCA9685Device.PRESCALE));

        driver.setPWMFrequency(200);
        mockDevice.dumpRegisters();
        assertEquals(30, mockDevice.readRegister(PCA9685Device.PRESCALE));

        driver.setPWMFrequency(30);
        mockDevice.dumpRegisters();
        assertEquals(202, mockDevice.readRegister(PCA9685Device.PRESCALE));

        mockDevice.dumpByteStream();
    }

After each test dumpRegisters is called to display the registers. This is just for convenience. Personally I like to be able to visually inspect the values at this point of the development. The output of the test looks like below.

[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Creating new servo driver Adafruit-PCA9685 for device Mock PCA9685 device
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting pwm frequency to 50 hz
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Read MODE 1 Register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Get prescale value for frequency 50
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Estimated pre-scale 121.0703125
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Final pre-scale 121
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Reading value of Mode 1 register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Mode 1 register 0
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting sleep bit on Mode 1 register 10
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing prescale register with 121
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing the old value back to mode1 register to start osc again
[main] INFO com.margic.adafruitpwm.mock.MockPCA9685Device - Registers:
00000000 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 79 00 ..............y.

[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting pwm frequency to 200 hz
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Read MODE 1 Register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Get prescale value for frequency 200
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Estimated pre-scale 29.517578125
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Final pre-scale 30
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Reading value of Mode 1 register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Mode 1 register 80
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting sleep bit on Mode 1 register 10
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing prescale register with 30
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing the old value back to mode1 register to start osc again
[main] INFO com.margic.adafruitpwm.mock.MockPCA9685Device - Registers:
00000000 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 1E 00 ................

[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting pwm frequency to 30 hz
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Read MODE 1 Register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Get prescale value for frequency 30
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Estimated pre-scale 202.45052083333334
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Final pre-scale 202
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Reading value of Mode 1 register
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Mode 1 register 80
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Setting sleep bit on Mode 1 register 10
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing prescale register with 202
[main] DEBUG com.margic.adafruitpwm.AdafruitServoDriver - Writing the old value back to mode1 register to start osc again
[main] INFO com.margic.adafruitpwm.mock.MockPCA9685Device - Registers:
00000000 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000080 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000090 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000A0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000B0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000C0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000D0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000E0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
000000F0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 CA 00 ................

[main] INFO com.margic.adafruitpwm.mock.MockPCA9685Device - ByteStream:
00000000 10 79 00 80 10 1E 80 80 10 CA 80 80             .y..........


Process finished with exit code 0

The next steps are to work on the actual servo registers and better defining the servo driver interface. Once thats done I'm looking forward to checking out the output on the pocket oscilloscope I mentioned in my previous post pocket-oscilloscope-for-measuring-pwm

I'll post some examples and some sample outputs when that part works.

2 comments: