Log in

View Full Version : Need help with PWM 1-2ms pulse control


Generalx5
30-04-2008, 14:51
Hey everyone,

How do I make my analog control the PWM outputs? I have a sliding pot that I wil be using, and I want the pwm to pulse 1.5ms in neutral when the slider is near the middle, all the way up for 255 and bottom with a 0. Anywhere in between becomes variable speed. Im new to programming, so any help would be awesome.

I just want to be able to make my servo adjust to my analog input. Much like a RC transmitter and receiver.

P.S. I am using the old smaller IFI Mini robot controller.

Mark McLeod
30-04-2008, 15:11
The old EDU robot controller has analog inputs with values from 0 to 1023.
The pwm outputs are sent values from 0 to 254.
So just scale the input to match the output like so

pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 1023;

tdlrali
30-04-2008, 15:14
pwm01 = Get_Analog_Value(rc_ana_in01) * 254 / 1023;That will overflow unless everything is casted as long, like this:

pwm01 = (char) (((long) Get_Analog_Value(rc_ana_in01)) * 254L / 1023L);

Mark McLeod
30-04-2008, 15:17
Sorry about that :ahh:
I corrected my post, but took the simplest route to make the calculation happen as a long.
Implied typecasts occur for the other parts. Explicit typecasting all around is safer though.

Thanks!

billbo911
30-04-2008, 15:47
The old EDU robot controller has analog inputs with values from 0 to 1023.
The pwm outputs are sent values from 0 to 254.
So just scale the input to match the output like so

pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 1023;


Wouldn't it be just as accurate to do this?

pwm01 = Get_Analog_Value(rc_ana_in01)/4;

Mark McLeod
30-04-2008, 15:50
Wouldn't it be just as accurate to do this?

pwm01 = Get_Analog_Value(rc_ana_in01)/4;
Yes, but perhaps not as instructive. >>2 ;)

Generalx5
30-04-2008, 16:00
Um...im getting an analog value from 0 - 22. or 0-23, is this right? I thought it was supposed to be from 0 to 254?

Mark McLeod
30-04-2008, 16:06
Um...im getting an analog value from 0 - 22. or 0-23, is this right? I thought it was supposed to be from 0 to 254?
Nope, not right, so something is odd.
The direct analog inputs on the EDU controller have 10-bit resolution, so you should see 0-1023 returned as values.
If you are talking about the analog input from a radio transmitter joystick, then that value is indeed 0-254.

How exactly are you printf-ing the value? There may be a simple error there. At least that's where I'd check first.

I assume it looks something similar to:

printf("Analog input = %d\r\n", (int) Get_Analog_Value(rc_ana_in01));


Is the analog input port initialized properly? e.g., Set_Number_of_Analog_Channels(ONE_ANALOG); // one or more

tdlrali
30-04-2008, 16:25
What's the range of resistance of the pot?

Generalx5
30-04-2008, 16:26
I did mine like this:

printf("Analog In = %d\n", Get_Analog_Value(rc_ana_in01));

But thats right though, the readings im getting are still 0-22. could it be that I've got the pot hooked up wrong? I have it connected to ground and signal. Its a 10k pot, and I tested it on the multimeter.

Could it be something in the user_initialization? Im using the EDU Default code right now to debug this problem.

Set_Number_of_Analog_Channels(TWO_ANALOG);

Mark McLeod
30-04-2008, 16:37
Here's how the analog should be hooked up, so that's one problem.
This is from the IFI EDU guide: http://www.ifirobotics.com/docs/legacy/edu-rc-2004_ref_guide_2004-mar-1.pdf

Pots are hooked up the way you have yours on the FRC Operator Interface, but not on the robot controller, so it can be confusing.

P.S. Minor notes:
I believe the 2004 EDU version of printf expected signed ints as inputs. Get_Analog_Value is defined as an unsigned int, so as a matter of principle I'd typecast it when printing it, however, with such a low value you won't see any difference.

Felix is refering to the fact that the EDU analog inputs are optimized for 100k pots, but while using a 10k pot would affect your full range somewhat, you shouldn't really notice.

Generalx5
30-04-2008, 17:13
Yeap, I have it done correctly, With the analog input, the signal pin is always logic high, and all I did was plug in a var pot from ground to the signal pin. Is as if you plugged in a solid resistor in its place.

Im sure its done right, as I have used this pot on other robots. So what could be the problem? Is it maybe because Im using V7.5 MPLAB? Could that be it? I tried 7.2 but it wouldnt open any projects.

Generalx5
30-04-2008, 17:48
Well. Forget about that, I dont know why, but im leaving it as:

pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 22;

This actually works out to 0 being full reverse, and 254 being full forward, and 127 bing neutral.

My analog inputs read 0 - 22 for some reason.

Thats the easy part, heres the harder part.

What do I need to do to make the PWM outputs respond like this:

http://img170.imageshack.us/img170/8396/24645603tn5.png

So set A and Set B represents the PWM values, it can be either one as a result of a simple switch.

And according to the adjustments on the pot, the speed on the servo varies in equal magnitude in either direction. If the pot is moved towards the top, The servo can be spining real fast depending on the switch. if the pot is near the bottom but not dead bottom. The servos can spin slowly in either direction, again, depending on the switch.

Has anyone attemped this before? Im totally clueless to where to start with this.

Mark McLeod
30-04-2008, 18:30
Normally the signal pin measures power coming in from the +5v power pin not flowing out to ground. (Taking Ben Franklin's view of electricity)
As long as you understand that your description of how you wired the pot is incorrect for normal operation, but you're happy with it then there's no problem. You've just wired it as a voltage divider that's all. I just don't want anyone else reading this to get the wrong idea though.

If you want only the seven specific values in Set A or B then you'd use a lookup table or nested If statements. If you want variable speeds then just map 0-22 to the corresponding range of pwm values, e.g., 0-22 mapped to 0-127 for Set B.

For a lookup table, you create an array with one entry for each of the possible 23 values that your pot could be. Then you pull values from the array using the analog input as an index.

static unsigned int pwm_setB[23]={0,0,0,25,25,25,47,47,47,64,64,64,80,80,80,100,10 0,100,127,127,127};

pwm01=pwm_setB[Get_Analog_Value(rc_ana_in01)]; // If Set B

Nested IFs would look like:

unsigned int value;
value = Get_Analog_Value(rc_ana_in01);

if (value < 3)
pwm01 = 0;
else if (value < 6)
pwm01 = 25;
//etc.


P.S. Be aware that the pot value may jump around a little even if you aren't touching it, so if you're right on the edge, say 3, then you might see the servo twitching between two of your set B speeds.

Alan Anderson
30-04-2008, 18:57
Yeap, I have it done correctly, With the analog input, the signal pin is always logic high, and all I did was plug in a var pot from ground to the signal pin. Is as if you plugged in a solid resistor in its place.

Im sure its done right, as I have used this pot on other robots. So what could be the problem? Is it maybe because Im using V7.5 MPLAB? Could that be it? I tried 7.2 but it wouldnt open any projects.

You have not done it correctly. It is not done right. Look at the picture Mark provided. The analog input pin wants you to supply a voltage, but according to your definition you're only supplying a resistance. There is a high-valued resistor connected internally from the analog input pin to +5, so your variable resistance combined with that does end up forming a voltage divider (a maximum 10k ohms gives a maximum output of about 1/50 of full scale). If you want to get the proper results you really need to pay attention to the proper wiring for a potentiometer.

Generalx5
01-05-2008, 00:34
Alright...I've resoldered the pot now, and its working nicely.

....now into the problem with the casting Set A and Set B. Im not sure what you mean by casting 0 - 126 to Set A and casting 127 - 254 to Set B.

Do you mean that When the switch is set to A side, it will only activate PWM pulses between 0 - 126? and the same with side B ( 127-254)?

How should I seperate the pulses into two groups? Ive only been able to get 1 side of it working. I doubled the numerical value of the pot and that gave me the ability to control PWM pulses between 0 - 127. So it would look something like this:

Before pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 1024;

After pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 2048;

Im stumped. Sorry for all these questions, Im not a programmer, but I need to figure this out, so any help is much appreciated.

yongkimleng
01-05-2008, 02:24
on the software side, heres my take.

I read that you want to map the analog pot directly to the pwm out right, so heres what I'd do:

pwm01 = Get_Analog_Value(rc_ana_in01) >> 2;

Now for the question on having two "sets".

Assuming you are getting full 10 bits range now and you want two sets
char set = 1; // set to 0 for the other set, or hook a switch up to this variable.

if(set == 1) {
// 127 to 254
pwm01 = (Get_Analog_Value(rc_ana_in01) >> 3) + 127;
} else {
// 127 to 0
pwm01 = 127 - (Get_Analog_Value(rc_ana_in01) >> 3);
}

maybe you need to add some casting above but on overall it should be correct.

Explanation:
Get_Analog_Value(rc_ana_in01) >> 3

Previously it was '>> 2', because I'm doing a bit shift so that the range from 0-1023 becomes 0-254 (8bits)

Now its >>3 as I'm sizing the range to 0-127 (7 bits) to be used in the calculation to scale set 1 (127 to 254) or set 2 (127 to 0).

Mark McLeod
01-05-2008, 09:10
....now into the problem with the casting Set A and Set B. Im not sure what you mean by casting 0 - 126 to Set A and casting 127 - 254 to Set B.

Do you mean that When the switch is set to A side, it will only activate PWM pulses between 0 - 126? and the same with side B ( 127-254)?

How should I seperate the pulses into two groups? Ive only been able to get 1 side of it working. I doubled the numerical value of the pot and that gave me the ability to control PWM pulses between 0 - 127. So it would look something like this:

Before pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 1024;

After pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 2048;

I think you're a programmer now :) and an electrician, you're just learning more stuff.

James' suggestions get into some interesting low-level programming tricks that you probably want to bypass for now.
The same with the other math shortcuts suggested. Understand how you map from one range to another then you can play with shortcuts. (Nobody likes long division...)

You already mapped one range into another when you took the analog 0-1023 and mapped it into the range 0-254.
It's the same for going to the range 0-127 or 127-254 or any other range of data.

It's just a ratio: analog input / analog full range = pwm output / pwm full range
or
input/1024 = pwm01 / 254

With a little math manipulation that ratio can be expressed as:
pwm01 = (analog input) * 254 / 1024
where the 254 is your full pwm range (the range you want to end up at) and 1024 is the full analog input range (the range you start with).
That's why replacing 1024 with 22 worked for you earlier. Your full range then was just 22.

Instead of your answer:
After pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 254 / 2048;

Keep the ranges you are working with (starting with a range of 1024 and ending up with a range of 127) and you'll get a more understandable result:
After pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 127 / 1024;

----------------
Here's the curve ball:
All that works fine for ranges starting at zero, and it's not too hard to work the math around to a range of 127-254 by just adding 127. There is an additional robotics trick here though that throws a wrench into this second range, because 127, in the middle, is neutral.

You should think about how you want your servo to behave when you switch between the two Sets. By just shifting the range up by adding 127 your servo will be at rest when the pot is all the way up, but at full speed when you throw the switch.
You might want to think about having the pot be the same for both sets when it's in one position. That just means inverting the second Set range and making it 254-127 instead. Mapping an analog input of 0 to be 127 for both Sets will have your servo stopped for both Sets when the pot is all the way down, and at full speed in both when it's all the way up.

Care to take a guess on how to invert the range 127-254 to make it 254-127 instead?
You could instead invert 0-127 to make it 127-0 if that's easier to think about. Then add 127 to that.

-------------------------------------
Switches:
Using the default EDU code the first two ports (1 & 2) are analog inputs, the others are mixed digital INPUTS (6,8,10,12,14,16) or OUTPUTS (all the others). You can change these if you want where you saw the TWO_ANALOG setup before.
Make sure you use an INPUT and just hook a switch between the signal and ground (DO NOT use the center +5v power pin).
It'll appear in the code with a value of 0 or 1 and the code to check it looks like:

pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 127 / 1024; // It's going to end up in a range of 127 for either Set A or B

if (rc_dig_in06 == 1)
{
// Invert the value of pwm01 and move the range to 254-127
}
else
{
// Leave the range as it is
}

Generalx5
01-05-2008, 14:13
Haha, wow! You make it sound so simple. Heres my attempt....

I've been able to keep set B as Pwm=127 when pot is close to 0, and Pwm=255 when pot is close to 1023.

As for set A, I did the plus 127 method. Pwm= ((analog signal) * -127 / 1023) + 127

so that gets me close to Pwm= 0 when the pot goes to 1023.
close to Pwm=127 when pot goes to 0.


This way, I've made the ratio a negative number, when I enter 1023 for the analog signal, the pwm will result in -127, so by adding 127 at the end, I will obtain 0 when the Pot is at 1023 or close to it. When the pot is close to 0, 0*(-127/1023) = -127 + 127, I get a 0 for the PWM.

So this actually works out to this situation:

Pot @ 1024 SetA Pwm= 0 SetB Pwm= 255
Pot @ 0 SetA Pwm=127 SetB Pwm=127

Il have to play with the numbers some more to give it a neutral band in the middle. This is all theory, but I've havnt tested it on my mini rc yet, so when I get back, ill' give it a try.

Generalx5
01-05-2008, 16:52
if (rc_dig_in06 == 1)
{
pwm01 = ((long) Get_Analog_Value(rc_ana_in01) * (127 / 1023) + 127);
}
if (rc_dig_in06 == 0)
{
pwm01 = ((long) Get_Analog_Value(rc_ana_in01) * (- 127 / 1023) + 127);
}

Tada! I think this is right, ill have to give it a try when I get home...

Alan Anderson
01-05-2008, 18:39
When you have two mutually exclusive tests like that, you can use the if statement's else clause.


if (rc_dig_in06 == 1)
{
pwm01 = (long) Get_Analog_Value(rc_ana_in01) * 127 / 1023 + 127;
}
else
{
pwm01 = (long) Get_Analog_Value(rc_ana_in01) * -127 / 1023 + 127;
}

Note that I've removed the parentheses around (127/1023). With them in place, the intermediate result of the division will be zero, and I believe you'll find the output never varies from 127. Instead, you want to defer the division until as late as possible so you don't lose bits that you need.



I'd actually have done it a little differently:
pwm01 = (int)127 + Get_Analog_Value(rc_ana_in01) * rc_dig_in06?127:-127 / 1023;
But that's probably more cryptic than it needs to be for your purposes.

yongkimleng
02-05-2008, 00:10
If you're driving a victor from this, I don't think you need to worry about deadband. The victor has quite abit of deadband around the 127 region.

As for a servo.. well it may jitter around (not so sure).

You may want to print out the values to screen to see how much its jittering at. Maybe add a small (0.1uf? 1nF?) capacitor from signal to ground for the analog input. That usually helps when my analog cable is non-shielded and very long...

Edit:
apologies for using bit shifting, so I shall add a bit to explain what it is

>> is essentially known as 'bit shifting'.

Skipping the complicated details, it is simply:
a >> 1 same as a / 2
a >> 2 same as a / 4
a >> 3 same as a / 8
a >> 4 same as a / 16
The difference is that a bit shift operation takes significantly lesser processing time compared to a divide (unless compiler optimized).

Similarly, a << 1 same as a * 2
a << 2 same as a * 4
and so on.

Generalx5
02-05-2008, 15:50
Thanks everyone! It works perfectly. Now im able to do exactly what I wanted =D. This might be a bit hard on the servo but I think it shouldnt be too bad. Im testing this on a small scale robot, just trying to come up with driving abilities...where at a flick of a switch, the robot can start driving backwards, essentially turning the rear end of the robot into the front. I think this helps the drivers a little since they dont move their joysticks in reverse.

Generalx5
02-05-2008, 18:46
Is it possible to define a fuction?

#define let_go pwm01 = ((long) Get_Analog_Value(rc_ana_in01) * (- 127 / 1023) + 127)

I need to set up an array of these functions, possibly even make it so that a bunch of pwms = to that function.

Generalx5
02-05-2008, 19:27
Oh wow!

Sorry guys, I've somewhat impatient, I've took the time to test this code with a #define.

#define Let_go P1 = ((long) Get_Analog_Value(rc_ana_in01) * ( 127 / 1023) + 127)

in this case, I defined Let_go to represent the P1 which is the pwm01. And everytime Let_go is called upon, the pwm output would correspond to the potentiometer that i am adjusting. Hahah this is so cool to see programming language turning into physical movement. Wonderful...

Generalx5
03-05-2008, 02:16
In the code, in the User_routine file, there is a fuction called Limit_Switch_Max && Limit_Switch_Min && Limit_Mix.

Do I need those? What is the purpose for that to be there? It appears to be some sort of drive system limiter. But is it okay it I delete that? Im using something else instead.


Are the interrupts better digital inputs than making more digital inputs from the 1-16 pins? Im just courious as to if maybe the interrupts may have an advantage over the others since by default the are digital inputs.

Mark McLeod
03-05-2008, 07:58
In the code, in the User_routine file, there is a fuction called Limit_Switch_Max && Limit_Switch_Min && Limit_Mix.

Do I need those? What is the purpose for that to be there? It appears to be some sort of drive system limiter. But is it okay it I delete that? Im using something else instead.


Are the interrupts better digital inputs than making more digital inputs from the 1-16 pins? Im just courious as to if maybe the interrupts may have an advantage over the others since by default the are digital inputs.

You don't need those functions in user_routines.c, so they can be deleted.
The Limit_Switch_Min/Max are examples of how functions work, while Limit_Mix is handy for making a joystick behave arcade-style.

For what you're doing the interrupt digital inputs will behave just like the other digital inputs, so you can simply use them.
The interrupt inputs have some special properties that make them useful in very time critical tasks. Measuring and reacting to things much faster than a hand operated switch. We take advantage of interrupts with special sensors or highly accurate timed events.

------------------
I'd take the parentheses away from your real code as Alan suggested in his previous post, or move them.
In the integer math you're doing it's important to do all your multiplications first followed by any divisions.

analog input * 127 / 1023 or (analog input * 127) / 1023
// rather than
analog input * ( 127 / 1023)

This starts getting into more quirks of programming, but because of the integer data types we're using, each step in the calculation only allows a whole integer result.

So if analog input = 512 (halfway on the pot)
analog input * (127 / 1023) would be calculated by the EDU as 512 * (0) because 127/1023=0.0254154 which gets truncated to the integer 0

If the parenthesis are removed (or moved) then
(analog input * 127) / 1023 = (512 * 127) / 1023 = 8704 / 1023 = 85.33333 = 85

So the order you do calculations becomes very important.

Generalx5
06-05-2008, 00:08
Great! I totally forgot about that, I just put those brackets on just in case I get errors, I've seen at least 100 syntax error messages in about 2 real hours spent on programming. =D But I am contempt with my code as of now, it works the way I thought it would have.

Thanks for all the great support! Especially Mr. McLeod! Thank you so much!