MRL servo control

I wrote to Mats earlier, so he has some background on this already :)
 
 
I think I may have uncovered a small bug in the Adafruit16CServoDriver MRL service.
See the corection that I've made in bold below in my local Adafruit16CServoDriver java build for details
 
 
The bug becomes apparent when using the servo slider in the MRL servo GUI
Try it and see...before and after the fix below
 
public void setServo(Integer pin, Integer pulseWidthOff) {
// since pulseWidthOff can be larger than > 256 it needs to be
// sent as 2 bytes
/*
 * log.debug(
 * String.format("setServo %s deviceAddress %s pin %s pulse %s", pin,
 * deviceAddress, pin, pulseWidthOff)); byte[] buffer = { (byte)
 * (PCA9685_LED0_OFF_L + (pin * 4)), (byte) (pulseWidthOff & 0xff),
 * (byte) (pulseWidthOff >> 8) }; controller.i2cWrite(this,
 * Integer.parseInt(deviceBus), Integer.decode(deviceAddress), buffer,
 * buffer.length);
 */
//setPWM(pin, 4095, pulseWidthOff); edited out by Acapulco Rolf 26th May 2017, should be as below
// setPWM(pin, 4095, pulseWidthOff) // no worky code 
setPWM(pin, 0, pulseWidthOff); // This is worky code
}
 
Making the change shown and commented above corrects the issue
 
 
I've also "reworked" the calculations for the PCA9685 pre-scale calculation in the Adafruit16CServoDriver as below. 
 
I've posted a spreadsheet here to show the full pre-scale range across all achievable PCA9685 frequency points
 
Note that to get close to 330Hz (MG PDI 6221MG servo working frequency), set the frequency of the PCA9685 driver to 320Hz in order for the frequency pre-scale value to come in as close as possible under 330Hz.
 
The PCA9685 will hit 320Hz, given the precision of the pre-scale value (one byte)
 
I've measured this on an oscilloscope to prove the calculation
 
 
This is the calculation I now have as worky in my local PCA9685 MRL service Java code:
 
public void setPWM(Integer pin, Integer pulseWidthOn, Integer pulseWidthOff) {
.
.
.
.
 
// Acapulco Rolf pre-scale calculation
  prescale_value = Math.round(1.00663996 * osc_clock / precision / hz) - 1; 
 
// Acapulco Rolf correction for PCA9685 frequency overshoot 
// See issue #11 here: https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library/issues/11
// through the develper used a different correction different factor in his case
// and also see here: https://forums.adafruit.com/viewtopic.php?f=19&t=72554
 
  prescale_value = (float) (Math.round(prescale_value * 1.06)); 
 
  prescale_value = (int) prescale_value;
 
Yes, I could do this in one line, but I broke out the calculation to help me debug for now :)
 
// example PCA9685 pre-scale values and target frequencies     
// prescale_value = 106;  // for PCA9685 @ 60Hz   
// prescale_value = 17;   // for PCA9685 @356Hz
// prescale_value = 18;   // for PCA9685 @337Hz
// prescale_value = 19;   // for PCA9685 @320Hz
 
 
# See Acapulco Rolf PCA9685 spreadsheet for details
 
 
 
This following is worky for me now with my PDI 6221 MG servos running at their stated frequency of 330Hz:
 
servo2.moveTo(50)
time.sleep(1)
servo2.moveTo(55)
time.sleep(1)
servo2.moveTo(60)
time.sleep(1)
servo2.moveTo(65)
time.sleep(1)
servo2.moveTo(70)
time.sleep(1)
servo2.moveTo(75)
 
 
Of course, all this still requires hard coding servo max/min (SERVOMAX and SERVOMIN) in the MRL Adafruit16CServoDriver service, because of the logic below in servoMoveTo()
 
public void servoMoveTo(ServoControl servo) {
ServoData servoData = servoMap.get(servo.getName());
 
 
if (!pwmFreqSet) {
setPWMFreq(servoData.pin, defaultPwmFreq);
}
 
if (servoData.isEnergized) {
// Move at max speed
if (servoData.velocity == -1) {
log.debug("Ada move at max speed");
servoData.currentOutput = servo.getTargetOutput();
servoData.targetOutput = servo.getTargetOutput();
log.debug(String.format("servoWrite %s deviceAddress %s targetOutput %f", servo.getName(), deviceAddress, servo.getTargetOutput()));
int pulseWidthOff = SERVOMIN + (int) (servo.getTargetOutput() * (int) ((float) SERVOMAX - (float) SERVOMIN) / (float) (180));
 
 
So.....I've now removed the hardcoded servo constants in my local MRL dev build
 
I now pass in servo frequency, maximum rotation angle, and minimum and maximum servo pulse width as parameters of the servo.attach() method, in order to define these servo attributes at run time
 
edit:
I note that the microsecondsToDegree method also hardcodes servo end points (eek)
 
public double microsecondsToDegree(int microseconds) {
if (microseconds <= 180)
return microseconds;
return (double) (microseconds - 544) * 180 / (2400 - 544);
}
 
With my code changes of the servo class, these endpoints are now user definable at runtime :)
 
@Kwatters/@Grog/Mats and the gang, any thoughts...?
 
 
By the way, I'd like to display these new servo attributes in the MRL servo GUI.
Are you able point me at where I can get to that specific code in the mrl servo GUI source code 
 
I've got the MRL development branch compiled locally in Eclipse
 
JX PDI 6221 MG servos worky in MRL @ 330Mhz
 
calamity's picture

Hi acapulco Rolf I don't

Hi acapulco Rolf

I don't think the method microsecondsToDegree(int) is used anymore. I think it's a fossil from my first implementation to use microseconds for servo position. Now the conversion to microsecond happen in the Arduino class and better that way, and should allow you to use other value for other servo controller.

 

Acapulco Rolf's picture

PCA9685 driver

Hey calamity

Good to know :)

@Mats, a further update for you

 
From what I'm reading the internal clock on the PCA9685 is not particularly accurate
 
See here
 
I've picked up a few of these PCA9685 PWM boards
They have an external clock which can be optionally enabled in software
 
See the code here and here
 
I've hooked one up to an Arduino and have taken some measurements
 
I've included a fourth servo mode in the example source code specific to my servos for their 500 to 2500 pulse width
 
 
 
My measurements appear to show that the frequency accuracy of the Hobbytronics controller diminishes at 330Hz but no matter :) 
 
See screen capture below where the requested frequency is 330Hz but the actual frequency obtained is 324Hz
 
300Hz (note the significant delta)

 
 
Other frequencies up to the 330Hz are all accurate apart from at 250Hz, where the frequency obtained dips slightly. 
 
See below
 
50Hz 

 
60Hz

 
 
100Hz

 
 
150Hz

250Hz (note the measured discrepancy)

 
300Hz

 
My servos are running well at 60Hz, so I'll stick to that frequency
 
I'm not yet decided if I'll swap the Adafruit PCA9685 controller for the Hobbytronics PCA9685 controller 
 
For reference again, my specific servo spec is as detailed here:
 
 
I'm about to write a specific MRL service for this Hobbytronics PCA9685 controller so we can take advantage of it's external clock 
 
I'll base it on your MRL AdafruitPCA9685 service 
 
Once I've written and tested the new service I'll let you take a look. :)
Mats's picture

Hi I made the suggested

Hi

I made the suggested change from 

// setPWM(pin, 4095, pulseWidthOff) // no worky code 
setPWM(pin, 0, pulseWidthOff); // This is worky code
 
I tested and both alternatives work with my hardware. But I agree that the suggested change is correct. It's made in version 2257.
 
/Mats
Ray.Edgley's picture

There is another possible bug

I've noted that when shutting down MRL on a Raspberry Pi, servos controlled by the AdafruitPCA9685 service that were not detached manually first will still be driven until the power is cycled or the MRL program is run again and the servos attached and then detached.

 

There does not appear to be detach sequence built into MRL when shutting down.

The arduinos apeare to detach themselves when they loose coms with the MRL service.

This is just an observation.

Ray

 

 

Mats's picture

Servo detach

Yes. It's a problem. I think MRL has methods to make a graceful shutdown, but them are not implementera in all Services.