control Servos using Micro controller ?!

Thread Starter

Mashadawy

Joined Jan 6, 2012
4
Hey, I am new here at this forum and I didn't know where to put ma question !
so anyway, I was thinkin about controlling several servo motors with PIC Micro-controller using C lang.
I thought of PWM to achieve that task, but unforchantly I only had two CCPs in my chip, so it will not work for my "multi-channel servo controller"..

and there is another thing, do the servos has only 3 positions "0,90,180" or they can be in between like "60 degree" or whatever ?!
If they could! how can I control them using potentiometers ?

BTW.. I am using PIC 16f877a.

any Ideas ?!
 

spinnaker

Joined Oct 29, 2009
7,830
You can use any binary output pin for PWM. You just need to "bit bang" the pin. Not nearly as efficient or easy as a hardware PWM peripheral but it can be done.

Also there is nothing wrong with having multiple MCUs.
 

Thread Starter

Mashadawy

Joined Jan 6, 2012
4
I found that C prog that used to control 2 servos (or more), but I couldn't get how it is gonna work..
if anyone could demonstrate the idea of the how it is working or the sequence of the operation, I would be grateful..

Rich (BB code):
/*
Written by: Chris
http://www.pyroelectro.com

Tutorial: Servo Motor Control
http://www.pyroelectro.com/tutorials/servo_motor/index.html

November 17, 2007
*/

#include <p18f452.h>
#include <timers.h>
#include <delays.h>

//The servos are all set to go to an initial 
                        //state when the system is powered on

int servo0 = 0xF63B; // Servo 0
int servo1 = 0xF077; // Servo 1

//Servos 2, 3 & 4 are not used however they are included
//        to show how multiple servos could (can) be used.
int servo2; // Servo 2
int servo3; // Servo 3
int servo4; // Servo 4

int count = 0;

void InterruptHandlerHigh (void);

void main(void)
{

TRISB = 0x00;
PORTB = 0x00;

RCON = 0b000000000; 
INTCON = 0b10100000;

OpenTimer0( TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_2 );
OpenTimer1( TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_INT & T1_PS_1_2 & T1_OSC1EN_OFF & T1_SYNC_EXT_OFF );

/*
How Timer Calculations Work:
Both timers are set to 1:2 Prescaled values. The timers will count every instruction cycle executed as 2.

(1) Since servos use a 50Hz frequency, and, 1/50Hz = 20mS, we need to generate a timer for 20mS

(2) Pic Clock Frequency = 20 MHz = 20,000,000 Hz

(3) Pic Instruction Cycle Frequency = 20 MHz / 4 = 5 MHz = 5,000,000 Hz

(4) (0.020 Seconds) / 5,000,000 Hz = 100000 Instruction Cycles

(5) Timer counts every instruction as 2 ~~ 100000 / 2 = 50,000.

(6) Convert that value to Hex: 0xC350

(7) Finally, the timers count up from 0x0000, so subtract 0xC350 from 0xFFFF

(8) Final Value: 0x3CAF
*/

WriteTimer0( 0x3CAF );        //Trigger Interrupted after 20mS
WriteTimer1( 0xF63B );  //This is just a small initial delay chosen at random.
 

while(1){

        servo0 = 0xEC77;// Servo 0 - Move to 0 Degrees
        servo1 = 0xFB1D;// Servo 1 - Move to 90 Degrees
                Delay10KTCYx(250);        // 1 Second Delay
                Delay10KTCYx(250);
        servo0 = 0xF63B;// Servo 0 - Move to 45 Degrees
        servo1 = 0xF63B;// Servo 1 - Move to 45 Degrees
                Delay10KTCYx(250);        // 1 Second Delay
                Delay10KTCYx(250);
        servo0 = 0xFB1D;// Servo 0 - Move to 90 Degrees
        servo1 = 0xEC77;// Servo 1 - Move to 45 Degrees
                Delay10KTCYx(250);        // 1 Second Delay
                Delay10KTCYx(250);

}

}

//INTERRUPT CONTROL
#pragma code InterruptVectorHigh = 0x08                //interrupt pointer address (0x18 low priority)
void InterruptVectorHigh (void)
{
        _asm        //assembly code starts
        goto InterruptHandlerHigh                //interrupt control
        _endasm //assembly code ends
}
#pragma code
#pragma interrupt InterruptHandlerHigh        //enf.

void InterruptHandlerHigh()        // declaration of InterruptHandler
{//this gets ran when ever the timers flop over from FFFF->0000
        if(INTCONbits.TMR0IF)                        //check if TMR0 interrupt flag is set
        {
                                WriteTimer0( 0x3CAF );
                                WriteTimer1( 0xFC77 );
                                count = 0;
                            INTCONbits.TMR0IF = 0;                //clear TMR0 flag               
        }
        if(PIR1bits.TMR1IF == 1 && PIE1bits.TMR1IE == 1)        //if set controls the first servo
        {
                count++;        
                                switch(count){                
                        case 1:     PORTB = 0x01; // First Stage
                                            WriteTimer1( servo0 );
                                                break;
                        case 2:                PORTB = 0x02; // Servo 1
                                                WriteTimer1( servo1 );
                                                break;
                        case 3:                PORTB = 0x00;
                                                //PORTC = 0x02; // Swivel/Rotate
                                                WriteTimer1( servo2 );
                                                break;
                        case 4:                
                                                //WriteTimer1( servo3 ); 
                                                break;
                        case 5:                
                                                //WriteTimer1( servo4 ); 
                                                break;
                        case 6:                
                                                //WriteTimer1( 0 );
                                                break;
                                }

                PIR1bits.TMR1IF = 0;                        //clear Timer1 flag
                PIE1bits.TMR1IE = 1;                        //clear Timer1 enable flag set to zero
        }

    INTCONbits.GIE = 1;                                //re-enable all interrupts
}
 

spinnaker

Joined Oct 29, 2009
7,830
Just from an quick look. this appears to be the "bit banging" I mentioned. I am guessing they are using the timer to provide the frequency and duty cycle of the PWM waveform.
 

MrChips

Joined Oct 2, 2009
30,802
Proportional servos can be controlled to any position using pulse width modulation (PWM). Any micro can handle multiple servos. You do not need special timer or PWM outputs.
 

ErnieM

Joined Apr 24, 2011
8,377
Yes, modern servos can assume any position between -90 and +90 degrees depending on how long a pulse they get. 1mS turn to -90, 2 mS turns to +90, and anything in between. You need to update them every 20mS, so it is easy to drive 10 outputs thru just code and not a PWM module. 10 units * 2 mS pulse = 20 mS period.

Exactly how much a servo changes (-/+ 90 or -/+ 45 degrees) for a 1 mS is dependent on exactly which servo you use, so check it's data sheet carefully.

The PIC you picked has 8 analog to digital converters, so you can drive each A2D input off a pot connected to give you 0 to 5V signal (assuming your running at 5V). That gives you control of 8 servos for each PIC in your system.

The code you posted is a bit of a mess and is for a PIC18 series device, which uses a different compiler. It also has at least one bug I see offhand (you should not re-enable interrupts as he does in the last line). Also he uses two timers to drive his single output when you should be able to do everything with a single timer and some proper state machine code.
 

MMcLaren

Joined Feb 14, 2010
861
I wonder if you couldn't use the CCP module "special event" mode to drive up to eight channels with 1-usec pulse width resolution?

Regards, Mike

Rich (BB code):
;******************************************************************
;  K8LH Crazy-8 Hi-Rez 8-channel (PORTB) Servo Algorithm/Driver
;
;  1 usec steps, 600-2400 usecs range, using prescaler setting 
;  of 1, 2, or 4 with a 4, 8, or 16-MHz clock, respectively.
;
;
;   unsigned int Servo[] = { 1500, 1500, 1500, 1500, 1500,
;                            1500, 1500, 1500, 20000 };
;
;   void interrupt()            // special event interrupts
;   { static char n = 0;        // servo array index, 0..8
;     static char Channel = 1;  // channel output bit mask
;     pir1.CCP1IF = 0;          // clear spcl event interrupt
;     portb = Channel;          // output new Servo pulse
;     ccpr1 = Servo[n++];       // update "match" period value
;     Servo[8] -= ccpr1;        // adj end-of-period off time
;     Channel <<= 1;            // prep for next channel
;     if(n == 9)                // if end of 20-msec period
;     { n = 0;                  // reset array index
;       Servo[8] = 20000;       // reset the 20 msec period
;       Channel = 1;            // reset Channel to '00000001'
;     }
;   }
;
 
Last edited:

John P

Joined Oct 14, 2008
2,026
To expand on what Ernie M said, if you have 8 servos, you can divide the 20 msec frame into 16 intervals, where each pair of intervals is the "on" and "off" time for a single servo (with the time for all the other 7 servos added to "off"). You need to set up a variable timeout to generate these intervals, where every "on" and "off" add up to 2.5msec. You can use the same timing to run the A/D converter, because you also need 16 intervals there: in each case you first have to set the multiplexer and allow it to settle, then do the actual conversion. You wouldn't have to check whether the A/D is complete--the servo intervals would always be longer than it needs.
 

Thread Starter

Mashadawy

Joined Jan 6, 2012
4
First of all, thx Ernie M.. but I was wondering how can I make the changes in the signal of A2D changes the duty cycle of the servo pulse... "Excuse me, I know it might be a dumb question but I am a little bit newbie with the Microcontroller stuff".

@John P, I didn't get how to generate these intervals using the timeout ?!
 

John P

Joined Oct 14, 2008
2,026
We're rapidly approaching the point of "It's time you worked some of this out for yourself."

However, since we're not there yet:
Consider using Timer1. If your crystal is 20MHz, then 2.5 msec is 12500 clock cycles, 1msec (min servo on time) is 5000, and 2msec (max servo on time) is 10000, and the off time varies from 7500 to 2500. The easy way to do this is to stop the clock, load the timer registers (2 bytes) and restart the clock. You should check the LST file, but it's probably 5 cycles every time you do this. You might have to pull some tricks to get the compiler to do it right.

If the A/D reading is scaled 0-1024, then you just multiply it by 5 to get the desired servo on time; you may need to limit it to 5000.

I'd write something like this for the interrupt that occurs regularly, at a 2.5msec interval. The value of "mask" is 1 for channel 0, and the outputs appear on Port D. The A/D is set up for right-justified output.

The math definitely needs checking. Counting downward from zero is very difficult! It's there because the timer counts up to 0xFFFF and then resets, but you have to live with it.

Rich (BB code):
  portd = mask;
  mask <<= 1;                                      // Ready for next channel
  setting = ((adresh << 8) + adresl) * 5;
                    // Set the A/D mux for the next channel, I forget how it works
  if (setting > 4999)
    setting = 4999;                               // May not be necessary
  output_high_time = -4995 - setting;
  output_low_time = output_high_time - 12500;
  bit_clear(t1con, 0);                             // Stop the timer
  tmr1h = output_high_time >> 8;            // Make sure the compiler does this as two instructions
  tmr1l = output_high_time & 0xFF;          // Again, must be two instructions
  bit_set(t1con, 0);                              // Start the timer
Then when the timer gives you another interrupt, you'll set the output low:

Rich (BB code):
  bit_set(adcon, 2);                      // Fire A/D, and I think this equalizes the time
  portd = 0;                                // Output low
  bit_clear(t1con, 0);                             // Stop the timer
  tmr1h = output_low_time >> 8;            // Make sure the compiler does this as two instructions
  tmr1l = output_low_time & 0xFF;          // Again, must be two instructions
  bit_set(t1con, 0);                              // Start the timer
I would certainly check this with a scope and plan to correct it until it runs at the correct rate. But I'd start with something like this code.
 

ErnieM

Joined Apr 24, 2011
8,377
First of all, thx Ernie M.. but I was wondering how can I make the changes in the signal of A2D changes the duty cycle of the servo pulse... "Excuse me, I know it might be a dumb question but I am a little bit newbie with the Microcontroller stuff".
OK, to start with let's just say the servo turns from -90 to +90 degrees when the pulse width goes from 1.0 to 2.0 mS. If we connect a pot to an 8 bit A2D we get a conversion value out of 0 to 255. So to change the servo we can use the equation:

Time = 1 mS + A2D / 255
So as the A2D goes from 0 to 255 the time goes from 1 to 2 mS.

For a 10 bit A2D we just divide by 1023.

Another "gotcha": back when I was playing with a RC race car there was a small "zero" pot for the steering control to make it drive straight. So there were actually two controls (pots) per servo. Just something to mention early in the planning stages.

Sorry, wanted to get this off before I crash into bed as I don't think I will be on here tomorrow. I would normally figure out a few numbers to toss into the timer but not with my tired brain tonight.
 
Top