Saturday, March 21, 2015

Raspberry Pi Java PCA9685 PWM Driver - First servo pulses output

It's now time to turn attention to the servo output channels on the Adafruit PCA9685 device. I haven't completed any of the logic to set the servo angle but wanted to check the Java driver for the Raspberry Pi was working as expected. I needed to send a test Pulse Width Modulation (PWM) signal to one of the out put channels.

Calculating the register settings for a test pulse

A servo pulse is repeated x times a second. The specific frequency is not critical it should be somewhere around 50Hz for a typical servo. What is important is the pulse length. At 50Hz the the entire cycle is 20ms. A servo pulse is some where between 1ms and 2ms. That's between 5% and 10% of the cycle. Given we only have a max of 10% of the PWM to play with any errors are going to be fairly significant.

The PCA9685 uses 2 pairs registers per channel to set the pulse. It has a resolution of 4096bits, thats why it takes a pair of 1byte registers to represent a value with maximum of 4096. The value represents a count of time in the cycle. The first pair represents when the output will be turned on, the second when the output will be turned off.

To determine the frequency a pre scaler is used to scale the PCA9685 internal oscillator from 25MHz to the appropriate value. At 25Mhz 4096 counts would be over way to fast 0.16ms incase your wondering. (Ok I admit thats not actually possible, in fact the actual max of the output is 1Khz) The goal is to get a the modulation frequency to close to 50Hz.

The datasheet provides the formula for setting the prescaler to provide the expected frequency:
prescaleVal = round(oscClock/(4096 * updateRate)) - 1

My Java driver includes a method to set the PWMFrequency

    public int getPreScale(int frequency) throws IOException{
        LOGGER.debug("Get prescale value for frequency {}", frequency);
        
        double prescaleval = CLOCK_FREQUENCY;
        prescaleval /= RESOLUTION;
        prescaleval /= frequency;
        prescaleval -= 1.0;
        LOGGER.debug("Estimated pre-scale {}", prescaleval);
        prescaleval = Math.round(prescaleval + 0.5);

        if(prescaleval > 254){
            throw new IOException("Specified frequency " + frequency + " results in prescale value " + prescaleval + " that exceed limit 254");
        }
        int prescale = (int)prescaleval;
        LOGGER.debug("Final pre-scale {}", prescale);
        return prescale;
    }

To test this I needed to request to send known output to the output channel.
If the resolution is 4096 the time for that amount of counts is 20ms it simply 4096/20 = 204.8 to figure out how many counts represent a 1ms pulse, 1ms being the lowest servo pulse. I happened to round down to 204 or 0xCCh cause it was neater and is insignificant. I hard coded these to set the registers.

        try {
            device.writeRegister(PCA9685Device.LED0_ON_HIGH, (byte) 0x00);
            device.writeRegister(PCA9685Device.LED0_ON_LOW, (byte) 0x00);
            device.writeRegister(PCA9685Device.LED0_OFF_HIGH, (byte) 0x00);
            device.writeRegister(PCA9685Device.LED0_OFF_LOW, (byte)0xCC);
        }catch(IOException ioe){
            LOGGER.error("Error setting pulse", ioe);
        }


The four registers are set for LED0 (channel 0) the first two specify the count when it should turn on. This is set to 0 counts. The pulse will begin at the start of each cycle. 204 is less than 255 so the off high byte is 0 with 204(0xCCh) in the off low byte.
I hooked up the pocket scope to see what the output was. I was a bit surprise to see that the trace did not measure a full second. It's somewhere around 950µs. I wasn't expecting it to be off by that much.

I looked at a lot of sample code before starting my own driver. I had actually written some python code based on some example. I remember seeing one a comment in the code about some correction factor for the PCA9685. Turns out cyanogilvie on github reported an issues to adafruit Frequency accuracy issue

In that issue he reported that a correction factor of 0.9 on the frequency does the trick.

        double correctedFrequency = frequency * 0.9;  // Correct for overshoot in the frequency setting (see issue #11).
        double prescaleval = CLOCK_FREQUENCY;
        prescaleval /= RESOLUTION;
        prescaleval /= correctedFrequency;

With that correction I get the output below. Now it's much closer. It's still not perfect but it's within acceptable tolerances for my purpose now.

I had significant doubts that a $50 scope would be useful enough. I had read other people were looking to use it for measuring servo inputs. At least I can confirm its of some utility for that purpose.

I did not measure how close the cycle is to 50Hz as I mentioned it is not that important. The key factor is the pulse length.

I would recommend adding this correction. I have seen a lot of code out there that does not use this correction. At least I and two others have measured the output can validate it with these parameters.



6 comments:

  1. The information you have posted here is really useful and interesting too & here, I had a chance to gather some useful information, thanks for sharing and I have an expectation about your future blogs keep your updates please.
    Regards,
    JAVA Training in Chennai|J2EE Training in Chennai

    ReplyDelete
  2. thank you a lot for sharing this all-powerful weblog.Very inspiring and harmonious to steer too.wish you still allocation more of your ideas.i will definitely love to gate.! VB Audio Cable Download

    ReplyDelete
  3. it became a first rate unintentional to visit this nice of site and i am satisfied to recognize. thank you therefore lots for giving us a chance to have this possibility..! 1st Birthday Wishes For Son

    ReplyDelete