Log in

View Full Version : PWM 13-16 Replacement Code


Kevin Watson
13-01-2007, 23:30
For those who are using interrupts and don't like the jittery PWM outputs on channels 13 through 16, I've written replacement code for IFI's Generate_Pwms() function that uses the built-in CCP hardware to generate super accurate PWM pulses. The code also allows you to set the neutral point and gain for each output channel, giving you complete control of your servos and motors. As an example, this code allows you to map the entire 0-255 PWM range into a small fraction of the normal travel of a servo arm, which would come in handy if you wanted to make a more accurate range calculation to the green light.

I'd like to do a quick beta test to find any bugs or documentation problems before posting it to my web page. If you're interested in helping out, you can download the code here: http://kevin.org/frc/frc_pwm.zip. If you find any problems with code or documentation, please leave a message here.

-Kevin

Edit: Changed link to point to released code.

Noah Kleinberg
14-01-2007, 00:57
For those who are using interrupts and don't like the jittery PWM outputs on channels 13 through 16, I've written replacement code for IFI's Generate_Pwms() function that uses the built-in CCP hardware to generate super accurate PWM pulses. The code also allows you to set the neutral point and gain for each output channel, giving you complete control of your servos and motors. As an example, this code allows you to map the entire 0-255 PWM range into a small fraction of the normal travel of a servo arm, which would come in handy if you wanted to make a more accurate range calculation to the green light.

I'd like to do a quick beta test to find any bugs or documentation problems before posting it to my web page. If you're interested in helping out, you can download the code here: http://kevin.org/frc/frc_pwm_beta.zip. If you find any problems with code or documentation, please leave a message here.

-Kevin

Sounds realy useful, thanks Kevin! I'll try to test this out within a couple of days if I can.

bear24rw
14-01-2007, 19:44
So how exactly would this effect the servos controlling the camera? Would it make them move smoother, more accurate?

Looks cool

Noah Kleinberg
14-01-2007, 20:31
Is there any reason that you couldn't use "int"s to store your motor speeds and have a greater range of values with the higher precision obtained by using the CCP (that is, instead of using the gain and center #defines)? I looked through the code and don't see why this wouldn't work, what i mean is something like this as a change to the PWM() function:

void PWM(unsigned int pwm_13, unsigned int pwm_14, unsigned int pwm_15, unsigned int pwm_16)
{
// int temp_pwm_13;
// int temp_pwm_14;
// int temp_pwm_15;
// int temp_pwm_16;

// .........
// calculate the number of 100 ns timer ticks
// needed to match the desired PWM pulse width
temp_pwm_13 = (PWM_13_GAIN * ((int)pwm_13 - 127)) + PWM_13_CENTER;
temp_pwm_14 = (PWM_14_GAIN * ((int)pwm_14 - 127)) + PWM_14_CENTER;
temp_pwm_15 = (PWM_15_GAIN * ((int)pwm_15 - 127)) + PWM_15_CENTER;
temp_pwm_16 = (PWM_16_GAIN * ((int)pwm_16 - 127)) + PWM_16_CENTER;

// load the CCP compare registers
CCPR2L = LOBYTE(pwm_13);
CCPR2H = HIBYTE(pwm_13);

CCPR3L = LOBYTE(pwm_14);
CCPR3H = HIBYTE(pwm_14);

CCPR4L = LOBYTE(pwm_15);
CCPR4H = HIBYTE(pwm_15);

CCPR5L = LOBYTE(pwm_16);
CCPR5H = HIBYTE(pwm_16);

//..............


Also, could CCPR1L and CCPR1H (or 2-5) each be 255? I ask this because with the default GAIN and CENTER values, the maximum I believe is 21400 (that is for the two combined as one word), as opposed to 65535.

Kevin Watson
14-01-2007, 20:44
So how exactly would this effect the servos controlling the camera? Would it make them move smoother, more accurate?

Looks coolOther than getting rid of the spasms, the coolest benefit (at least in my mind) is to give you more accurate control over your camera tilt servo, which can be used to calculate the range to a green light. Instead of the 0-255 range moving the servo ~180 degrees from floor to ceiling, wouldn't having the 0-255 range move the servo only 30 degrees be more useful? You gain angular precision, which gets you better range estimates. While I don't think its FIRST legal, I've been experimenting with digital servos in my pan/tilt assembly with fairly spectacular results.

-Kevin

Kevin Watson
14-01-2007, 20:52
Is there any reason that you couldn't use "int"s to store your motor speeds and have a greater range of values with the higher precision obtained by using the CCP (that is, instead of using the gain and center #defines)? I looked through the code and don't see why this wouldn't work, what i mean is something like this as a change to the PWM() function...Yes, certainly! I didn't do it that way because I wanted backward compatibility with Generate_Pwms(). You could have any units you want, even degrees. Cool, eh?


Also, could CCPR1L and CCPR1H (or 2-5) each be 255? I ask this because with the default GAIN and CENTER values, the maximum I believe is 21400 (that is for the two combined as one word), as opposed to 65535.Sure, but having pulses as long as 6.5535 ms doesn't do you much good when controlling servos and victors, which want to see inputs ranging from about 1ms to 2ms.

-Kevin

robind
14-01-2007, 22:23
Looks great. I'll use it tomorrow and post back with results.

Mike Copioli
14-01-2007, 23:04
Hey Kevin? Does your code leave a pause between pulses sent from the CCP module? After looking at your code I did not see anything to indicate this. The reason I ask is because I have noticed that when continuous pulses are sent to a servo using the CCP module, the servo exhibits jitter. I have found that a 2ms delay or greater between pulses will stop this behavior. Have you encountered similar behavior?

Kevin Watson
14-01-2007, 23:38
Hey Kevin? Does your code leave a pause between pulses sent from the CCP module? After looking at your code I did not see anything to indicate this. The reason I ask is because I have noticed that when continuous pulses are sent to a servo using the CCP module, the servo exhibits jitter. I have found that a 2ms delay or greater between pulses will stop this behavior. Have you encountered similar behavior?Like Generate_Pwms(), my code only generates one pulse on each of the four outputs each time it's called. If called from Process_Data_From_Master_uP(), the update rate is ~38 Hz. Typical servos won't handle a rate much higher than this, but the Victors are okay to at least 100 Hz.

-Kevin

Mike Copioli
15-01-2007, 09:54
Like Generate_Pwms(), my code only generates one pulse on each of the four outputs each time it's called. If called from Process_Data_From_Master_uP(), the update rate is ~38 Hz. Typical servos won't handle a rate much higher than this, but the Victors are okay to at least 100 Hz.

-Kevin

Ok, so does that mean you can not call it in the fast loop? If this is the case, is there an advantage to using PWM's 13-16? If the outputs can only be updated every 26.2 ms (38Hz) For example if I wanted to use 13 and 14 for camera servo control, to move 200 steps would take over 5 seconds if it moved in one step increments (200 * .0262s). I really would like to see the camera "snap" to a position. It seems that nasty little 26.2ms loop time keeps getting in the way.

kaszeta
15-01-2007, 11:12
Is there any reason that you couldn't use "int"s to store your motor speeds and have a greater range of values with the higher precision obtained by using the CCP (that is, instead of using the gain and center #defines)?

You can do this. Last year I cobbled together a similar set of routines for controlling PWM13 and 14 for the camera with 10 bit precision instead of 8 bit precision. I'll see if I can still find the code (we ended up not using the camera in the end, so that code got removed since it wasn't needed).

Kevin Watson
15-01-2007, 12:55
Ok, so does that mean you can not call it in the fast loop? If this is the case, is there an advantage to using PWM's 13-16?Yes, you could call it in the fast loop to control your Victors at a higher rate for applications like position control (which generally works better at rates greater than 38 Hz). You'll need to synchronize the updates to a timer or some sensor like a gyro.

I've already enumerated some other advantages above.

If the outputs can only be updated every 26.2 ms (38Hz) For example if I wanted to use 13 and 14 for camera servo control, to move 200 steps would take over 5 seconds if it moved in one step increments (200 * .0262s). I really would like to see the camera "snap" to a position. It seems that nasty little 26.2ms loop time keeps getting in the way.Your searching algorithm needs tuning. The camera has a fairly wide field of view, so I think you could use a much larger step. I would step in 30 degree increments, wait a bit for the camera to send a t-packet (or two) and then either start tracking, or step another 30 degrees... If you're using my code, these are built-in parameters you can tweak.

For grins, I used digital servos (not FRC legal) with my code and after modifying tracking.c a bit, I can find a green light and start tracking in somewhere between one and two seconds, without the software having any a priori knowledge of where the light is located. I suspect nearly the same level of performance can be had with the HS322HD servos.

-Kevin

Mike Bortfeldt
15-01-2007, 15:53
Kevin,

I would expect that the code should work fine. We (1126) used my version throughout the 2006 season without any issues. Your increase in gain from 40 to 50 is probably a good idea, as we found one of the Victors did not quite go to maximum at the high end (2 msec) without recalibration. The additional 25% on range should easily eliminate this potential problem. You may want to think about modifying your "temp_pwm_xx" calculation so it doesn't require the signed int multiplication. This adds a fair amount of execution time to the routine (relative to how quick it could execute, not to overall processor usage which is extremely minor). It also requires the use of the MATH_DATA section so if someone ever wanted to use the routine from within an interrupt (why? I don't know, but I never rule anything out), they would need to save that data section. I do like the addition of the CENTER & GAIN options it allows for much more flexibility.

Mike

AdamHeard
15-01-2007, 16:03
So... is the legality of this confirmed?

-Adam

Kevin Watson
15-01-2007, 16:39
Kevin,

I would expect that the code should work fine. We (1126) used my version throughout the 2006 season without any issues. Your increase in gain from 40 to 50 is probably a good idea, as we found one of the Victors did not quite go to maximum at the high end (2 msec) without recalibration. The additional 25% on range should easily eliminate this potential problem. You may want to think about modifying your "temp_pwm_xx" calculation so it doesn't require the signed int multiplication. This adds a fair amount of execution time to the routine (relative to how quick it could execute, not to overall processor usage which is extremely minor). It also requires the use of the MATH_DATA section so if someone ever wanted to use the routine from within an interrupt (why? I don't know, but I never rule anything out), they would need to save that data section. I do like the addition of the CENTER & GAIN options it allows for much more flexibility.

MikeThanks. To be compatible with Generate_Pwms() and the Victors, I needed to use a 5 us step. Servos seem to like a narrower range of about 1.1 ms to 1.9 ms (gain = 32).

I used the signed int so that I don't have to worry about underflow when the subtraction takes place. I'll have another look to see if I can move the terms around to get rid of the int.

-Kevin

Kevin Watson
15-01-2007, 16:47
So... is the legality of this confirmed?

-AdamThere are no rules preventing teams from using this code, so there is nothing to confirm. If you're thinking that this somehow gets around the ability to safe motors, it doesn't because IFI's mechanism for doing that is under the master microcontrollers control.

-Kevin

Matt Krass
15-01-2007, 17:33
There are no rules preventing teams from using this code, so there is nothing to confirm. If you're thinking that this somehow gets around the ability to safe motors, it doesn't because IFI's mechanism for doing that is under the master microcontrollers control.

-Kevin

I'm curious how that works, since the pins are supposedly connected straight to the user processor, how would the master processor disable that?

Also, how did you get around the interrupt-glitch thing? I'm missing something obvious when I look at the code...

Dave Flowerday
15-01-2007, 17:43
I'm curious how that works, since the pins are supposedly connected straight to the user processor, how would the master processor disable that?
Most likely the PWM outputs from the user processor run through a buffer with an enable line, and the master disables the buffers when the robot is to be disabled, which tri-states the buffer outputs.

JSonntag
15-01-2007, 18:01
Also, how did you get around the interrupt-glitch thing? I'm missing something obvious when I look at the code...

He disabled the interrupts when the pwm's transition from low to high

Kevin Watson
15-01-2007, 18:17
I'm curious how that works, since the pins are supposedly connected straight to the user processor, how would the master processor disable that?They aren't connected directly (which is why you can't use them as inputs). There is a switch that is controlled by the master that determines which processor gets to send its output to the pin. The call to Setup_PWM_Output_Type() tells the master processor who's in the driver seat (icky pun intended). When your 'bot is disabled, the switch is flipped to the master, which provides the 1.5 ms neutral pulse.

Edit: Actually, Dave's explanation may be closer to the truth. If the master is capable of generating the PWM output, why do we have the Generate_Pwms() kludge to deal with. Still, the 1.5 ms PWM neutral pulse needs to get generated for safety reasons. If not the master, who?

Also, how did you get around the interrupt-glitch thing? I'm missing something obvious when I look at the code...The pulse doesn't glitch because once software sets up the timer and CCP hardware and says go, everything is done in hardware, which is oblivious to the interrupts firing-off in the background.

-Kevin

Dave Flowerday
15-01-2007, 18:27
Still, the 1.5 ms PWM neutral pulse needs to get generated for safety reasons. If not the master, who?
If you're talking about when the robot is disabled, I'm pretty sure the PWM pins are left floating. We noticed a side effect of this last year when we ran some servo wires close to our RS232 for the camera, and when the robot was disabled the RS232 would generate enough noise on the (floating) PWM line to cause our camera servos to twitch. Actually, now that I think about it, if those PWM wires had been connected to a Victor, it could have been a dangerous situation. When the robot was enabled, the PWM output was being driven such that noise was not an issue.

Kevin Watson
15-01-2007, 18:32
If you're talking about when the robot is disabled, I'm pretty sure the PWM pins are left floating. We noticed a side effect of this last year when we ran some servo wires close to our RS232 for the camera, and when the robot was disabled the RS232 would generate enough noise on the (floating) PWM line to cause our camera servos to twitch. Actually, now that I think about it, if those PWM wires had been connected to a Victor, it could have been a dangerous situation. When the robot was enabled, the PWM output was being driven such that noise was not an issue.Ugh, floating the PWM outputs doesn't seem like a good idea. Thanks, I stand corrected.

-Kevin

maniac_2040
20-01-2007, 13:38
If you're talking about when the robot is disabled, I'm pretty sure the PWM pins are left floating. We noticed a side effect of this last year when we ran some servo wires close to our RS232 for the camera, and when the robot was disabled the RS232 would generate enough noise on the (floating) PWM line to cause our camera servos to twitch. Actually, now that I think about it, if those PWM wires had been connected to a Victor, it could have been a dangerous situation. When the robot was enabled, the PWM output was being driven such that noise was not an issue.

I've been seing something similar to this when testing on our robot. I was using some Vex Motor Modules to test some code. When I would put the controller in the program state to download, sometimes the motor would just randomly spin in all kinds of directions, and I would usually have to unplug the backup battery if it didn't stop. Does this pwm code fix that? I would like to know more about these "floating" pwms. When I start testing with our arm mechanism it will be very dangerous if the arm can just start randomly swinging around in the prog state...

Kevin Watson
21-01-2007, 16:31
IMHO, one of the nice things about this software is the ability to define how the PWM range of 0-255 maps to the servo position range. So for fun, I decided that I'd like to wring-out as much pointing accuracy from my camera's tilt mechanism by mapping the entire 0-255 PWM range to the more useful 0 to 90 degree tilt range. These are the steps I took:

1) Moved the tilt PWM from PWM output 2 to 16. This is necessary because PWM() can only contol PWM outputs 13 through 16.

2) The first piece of information I needed was to find the PWM pulse width that caused the servo to rotate the camera to the 90 degree position. Using the camera software's interactive PWM adjustment menu to command the tilt mechanism to rotate the lens up, I found that the default gain of 5.0 us per PWM step wasn't high enough to rotate the camera to the 90 degree position. I opened up pwm.h and increased to gain on PWM output 16 to a value of 70 for a pulse step size of 7.0 us. I re-compiled and downloaded the code to the RC and found that I could command the servo to 90 degrees with a PWM value of 254. Using the inverse of the algorithm from PWM(), I calculated that PWM() was sending a PWM pulse width of 2.389 ms.

3) Using the same technique described above, I determined the pulse width I needed to send for 0 degrees to be 1.479 ms.

4) I then calculated the pulse width range that I needed to cover by subtracting the result of #2 from #3 for a range of 0.910 ms.

5) To calculate the gain, I just divided the pulse range needed by the available PWM range of 256 for a gain of 36 or 3.6 us per PWM step. This is the gain value that is needed to limit the range of servo motion to 90 degrees.

6) I now needed to calculate a new center point for the servo. If I leave it at 1.5 ms, the servo will rotate through a 90 degree range, but it will be -45 degrees to 45 degrees. I needed to change the center point to +45 degrees so the servo will rotate from 0 to 90 degrees. To calculate the new center point, I just divided the range (from #4) by 2 and added that to the 0 degree pulse width (from #3) for a value of 19340 (or 1.934 ms).

7) Plugging the values from #5 and #6 into pwm.h, re-compiling, loading the RC, and then testing, I found the range to be the desired 0 to 90 degree range!

8) I then changed the PWM value to angle code in terminal.c from:

printf("Tilt Angle (degrees) = %d\r\n", (((int)TILT_SERVO - 144) * 25)/50);

to:

printf("Tilt Angle (degrees) = %d\r\n", ((int)TILT_SERVO * 90)/250);

9) The tilt gain was then changed from 8 to 4 for faster tracking.

10) Because I now have better pointing ability, I was also able to change the TILT_ALLOWABLE_ERROR_DEFAULT value to 3 pixels.

11) Finally, I changed the min tilt PWM value to 0, the maximum to 250 (255 is a degree or two past 90), adjusted the mid-point to 50.

This new found pointing accuracy might come in handy if you're using the green tracking light for navigation.

-Kevin

Alan Anderson
21-01-2007, 18:05
Using the Bells & Whistles camera code, PMWs 13-16 twitch for a moment when data is stored to EEPROM. I can work around it easily, but it's definitely undesired operation.

Kevin Watson
21-01-2007, 19:58
Using the Bells & Whistles camera code, PMWs 13-16 twitch for a moment when data is stored to EEPROM. I can work around it easily, but it's definitely undesired operation.Yeah, I noticed it this morning. I suspect the menu drawing code is using too much of the CPU and the servos are trying to return to the center position when they don't see a pulse for a while.

-Kevin

gnirts
21-01-2007, 22:36
Can I now use the alltimers library and PWMs 13-16 at the same time?

Thanks in advance,
Robinson

Kevin Watson
21-01-2007, 22:56
Can I now use the alltimers library and PWMs 13-16 at the same time?

Thanks in advance,
RobinsonYes, shouldn't be a problem.

-Kevin

pheadxdll
25-01-2007, 10:05
Is this feature included in the default 2007 code?

ace123
29-01-2007, 02:21
It's included in Kevin's camera codes at http://kevin.org/frc/
But it's not in the one you download at IFI (that still uses the old Generate_Pwms() function).

mluckham
05-06-2007, 11:33
IMHO, one of the nice things about this software is the ability to define how the PWM range of 0-255 maps to the servo position range. So for fun, I decided that I'd like to wring-out as much pointing accuracy from my camera's tilt mechanism by mapping the entire 0-255 PWM range to the more useful 0 to 90 degree tilt range. These are the steps I took:

1) Moved the tilt PWM from PWM output 2 to 16. This is necessary because PWM() can only contol PWM outputs 13 through 16.

2) The first piece of information I needed was to find the PWM pulse width that caused the servo to rotate the camera to the 90 degree position. Using the camera software's interactive PWM adjustment menu to command the tilt mechanism to rotate the lens up, I found that the default gain of 5.0 us per PWM step wasn't high enough to rotate the camera to the 90 degree position. I opened up pwm.h and increased to gain on PWM output 16 to a value of 70 for a pulse step size of 7.0 us. I re-compiled and downloaded the code to the RC and found that I could command the servo to 90 degrees with a PWM value of 254. Using the inverse of the algorithm from PWM(), I calculated that PWM() was sending a PWM pulse width of 2.389 ms.

3) Using the same technique described above, I determined the pulse width I needed to send for 0 degrees to be 1.479 ms.

4) I then calculated the pulse width range that I needed to cover by subtracting the result of #2 from #3 for a range of 0.910 ms.

5) To calculate the gain, I just divided the pulse range needed by the available PWM range of 256 for a gain of 36 or 3.6 us per PWM step. This is the gain value that is needed to limit the range of servo motion to 90 degrees.

6) I now needed to calculate a new center point for the servo. If I leave it at 1.5 ms, the servo will rotate through a 90 degree range, but it will be -45 degrees to 45 degrees. I needed to change the center point to +45 degrees so the servo will rotate from 0 to 90 degrees. To calculate the new center point, I just divided the range (from #4) by 2 and added that to the 0 degree pulse width (from #3) for a value of 19340 (or 1.934 ms).

7) Plugging the values from #5 and #6 into pwm.h, re-compiling, loading the RC, and then testing, I found the range to be the desired 0 to 90 degree range!

8) I then changed the PWM value to angle code in terminal.c from:

printf("Tilt Angle (degrees) = %d\r\n", (((int)TILT_SERVO - 144) * 25)/50);

to:

printf("Tilt Angle (degrees) = %d\r\n", ((int)TILT_SERVO * 90)/250);

9) The tilt gain was then changed from 8 to 4 for faster tracking.

10) Because I now have better pointing ability, I was also able to change the TILT_ALLOWABLE_ERROR_DEFAULT value to 3 pixels.

11) Finally, I changed the min tilt PWM value to 0, the maximum to 250 (255 is a degree or two past 90), adjusted the mid-point to 50.

This new found pointing accuracy might come in handy if you're using the green tracking light for navigation.

-Kevin


This is great Kevin, but I have always wondered why no one appears to be using the difference of the centroid from the CMUCAM centre pixel to compute angle-to-the-target (azimuth and elevation) instantaneously, rather than 'wasting time' moving the servos until the target is centred and THEN reading out the servo angle.

In my scenario - position the camera to a known azimuth and elevation and leave it there. As long as the target is detected by the camera, calculate the offset in pixels between the centroid coordinate and the centre pixel of the camera raster image. By experiment, calibrate the angle that this difference represents on your camera.

It doesn't eliminate the need for the pan/tilt servos - because the CMUCAM field of view is only a few tens of degrees - but it should be possible to add this optically-calculated angle to the current servo angles to get an angle relative to the robot itself. Your adjustments to map PWM values to 0-90 degrees would make this a snap.

Of course, this method would be useless while the servos are in motion as their precise angle would be unknown until they came to a full stop, although it could be estimated based on servo characteristics.

Am I missing something here?

ay2b
06-06-2007, 16:53
This is great Kevin, but I have always wondered why no one appears to be using the difference of the centroid from the CMUCAM centre pixel to compute angle-to-the-target (azimuth and elevation) instantaneously, rather than 'wasting time' moving the servos until the target is centred and THEN reading out the servo angle.
...
Am I missing something here?

There are teams doing that. 980 did last year. I believe 233 did 2 years ago. I'm sure there are others.