0
$\begingroup$

I am supposed to control a robotic arm with a servo motor. The angle of rotation of the servo motor depends on the voltage of a potentiometer.

A0 can read analog inputs and C2 can hold a PWM signal.

Here is my setup:

Diagram of potentiometer and servo

And here is my code:

void main() 
{
  int sensorReading;
  float x;

  TRISA = 0xFF;
  TRISC = 0x00;

  ADC_Init();
  PWM2_Init(3000);

  while(1)
  {
    sensorReading = ADC_Read(0);
    
    x = 500 + (sensorReading * 1500) / 1024;

    PWM2_Set_Duty(x/3000);
    PWM2_Start();
  }
}

However my robotic arm (servo motor) doesn't move at all

I am using the MikroC IDE and PIC18F452 as my microchip.

What am I doing wrong?

$\endgroup$
0

1 Answer 1

1
$\begingroup$

:EDIT:

Here's (I think) the solution. For servo control, typically you would want a 50 Hz frequency. For that, you'd want

PWM2_Init(50);

Then you'd want a range of values to use for the servo. Again, typically, it'd be 1000 to 2000 microseconds, but in your code it looks like maybe 500 to 2000 microseconds.

When I look at the manual, it's wanting a value from 0 to 255 for the duty cycle. The problem is that the duty cycle the manual refers to is for the full frame, but your application needs just a small subset of the frame.

Doing the math, if the carrier frequency is 50 Hz, then you have a frame width of 20 ms, or 20000 microseconds. Your band of operation is from 500 to 2000 microseconds, which means you want:

$$ \begin{array}{l} \text{min_val} &=& (500/20000)*255 \\ \text{min_val} &=& 0.025*255 \\ \text{min_val} &=& 6.375 \\ \end{array} $$ $$ \begin{array}{l} \text{max_val} &=& (2000/20000)*255 \\ \text{max_val} &=& 0.1*255 \\ \text{max_val} &=& 25.5 \\ \end{array} $$

You are expecting a value from 0 to 1023 on your sensor, and you need to map that range to 6.375 to 25.5, so you get:

$$ \begin{array}{l} \text{pwm_command} &=& 6.375 + \left(\frac{25.5-6.375}{1023-0}\right)*\text{sensorReading} \\ \text{pwm_command} &=& 6.375 + 0.0187 * \text{sensorReading} \\ \end{array} $$

Checking the math here:

  • Sensor reading of 0 gives 6.375. 6.375 divided by 255 gives 0.025, and multiplied by the frame width of 20 ms gives 500 microseconds
  • Sensor reading of 512 gives 15.95. 15.95 divided by 255 gives 0.06255, and multiplied by the frame width of 20 ms gives 1251 microseconds (halfway between min/max values)
  • Sensor reading of 1023 gives 25.5. 25.5 divided by 255 gives 0.1, and multiplied by the frame width of 20 ms gives 2000 microseconds.

So that should all work, except you are using a float for the command and it takes an unsigned short. The documentation at least makes no reference to allowing floats as inputs.

Does this work? I don't know. It depends on the servo you're using and how it handles out-of-bounds values. Some may vibrate/oscillate at values just beyond the acceptable ranges, some might bind, others could fail to respond.

Your safest bet would be to operate within the allowable limits there, meaning instead of trying to go to 6.375 as the minimum value, you go to the "safe" value of 7, because it's the first integer inside the [6.375, 25.5] range. Similarly, pick the maximum value to be 25, again because it's the last integer inside the range of values.

This also means that your neutral position of 15.95 isn't achievable, and you'll need to go for 16 instead. You're lucky that this is very very close to the calculated neutral position!

If I re-work the numbers with these values, I get:

$$ \begin{array}{l} \text{pwm_command} &=& 7 + \left(\frac{25-7}{1023-0}\right)*\text{sensorReading} \\ \text{pwm_command} &=& 7 + 0.0176 * \text{sensorReading} \\ \end{array} $$

Without writing out everything again, sanity check passes - A reading of 0 gives 7, 512 gives 16, and 1023 gives 25.

Putting this all together:

void main() 
{
  int sensorReading;
  int x; // <--- New, was float, now it is int

  TRISA = 0xFF;
  TRISC = 0x00;

  ADC_Init();
  PWM2_Init(50); // <--- New, was 3000 Hz, now 50 Hz

  while(1)
  {
    sensorReading = ADC_Read(0);
    
    x = 7 + 0.0176 * sensorReading; // <--- new mapping

    PWM2_Set_Duty(x); // <--- no division here
    PWM2_Start();
  }
}
$\endgroup$
2
  • $\begingroup$ Hi @Chuck.When my sensor reading is 1024 I want 2/3 of full cycle pulse in order to rotate the servo 90 degrees to the right.And when sensor reading is 0 I want the output HIGH for 500 us so I can rotate the servo 90 degrees to the left.So simply dividing sensorReading with 4 wont work because when sensorReading is 1023 I will get a pulse whose HIGH will be for all 3 seconds. $\endgroup$
    – Volpina
    Mar 30 at 21:58
  • $\begingroup$ @Volpina - ah somehow totally missed that you were trying to control a servo, I thought you were just going for a PWM signal. I rewrote the answer here, hopefully the snippet you need is at the end! $\endgroup$
    – Chuck
    Mar 31 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.