control Servos using Micro controller ?!

Discussion in 'Embedded Systems and Microcontrollers' started by Mashadawy, Jan 22, 2012.

  1. Mashadawy

    Thread Starter New Member

    Jan 6, 2012
    4
    0
    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 ?!
     
  2. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    4,887
    1,016
    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.
     
  3. Mashadawy

    Thread Starter New Member

    Jan 6, 2012
    4
    0
    thx spinnaker
    but isn't there another way using TMRs and Interrupts ??
     
  4. Mashadawy

    Thread Starter New Member

    Jan 6, 2012
    4
    0
    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..

    Code ( (Unknown Language)):
    1.  
    2. /*
    3. Written by: Chris
    4. http://www.pyroelectro.com
    5.  
    6. Tutorial: Servo Motor Control
    7. http://www.pyroelectro.com/tutorials/servo_motor/index.html
    8.  
    9. November 17, 2007
    10. */
    11.  
    12. #include <p18f452.h>
    13. #include <timers.h>
    14. #include <delays.h>
    15.  
    16. //The servos are all set to go to an initial
    17.                         //state when the system is powered on
    18.  
    19. int servo0 = 0xF63B; // Servo 0
    20. int servo1 = 0xF077; // Servo 1
    21.  
    22. //Servos 2, 3 & 4 are not used however they are included
    23. //        to show how multiple servos could (can) be used.
    24. int servo2; // Servo 2
    25. int servo3; // Servo 3
    26. int servo4; // Servo 4
    27.  
    28. int count = 0;
    29.  
    30. void InterruptHandlerHigh (void);
    31.  
    32. void main(void)
    33. {
    34.  
    35. TRISB = 0x00;
    36. PORTB = 0x00;
    37.  
    38. RCON = 0b000000000;
    39. INTCON = 0b10100000;
    40.  
    41. OpenTimer0( TIMER_INT_ON & T0_16BIT & T0_SOURCE_INT & T0_PS_1_2 );
    42. OpenTimer1( TIMER_INT_ON & T1_16BIT_RW & T1_SOURCE_INT & T1_PS_1_2 & T1_OSC1EN_OFF & T1_SYNC_EXT_OFF );
    43.  
    44. /*
    45. How Timer Calculations Work:
    46. Both timers are set to 1:2 Prescaled values. The timers will count every instruction cycle executed as 2.
    47.  
    48. (1) Since servos use a 50Hz frequency, and, 1/50Hz = 20mS, we need to generate a timer for 20mS
    49.  
    50. (2) Pic Clock Frequency = 20 MHz = 20,000,000 Hz
    51.  
    52. (3) Pic Instruction Cycle Frequency = 20 MHz / 4 = 5 MHz = 5,000,000 Hz
    53.  
    54. (4) (0.020 Seconds) / 5,000,000 Hz = 100000 Instruction Cycles
    55.  
    56. (5) Timer counts every instruction as 2 ~~ 100000 / 2 = 50,000.
    57.  
    58. (6) Convert that value to Hex: 0xC350
    59.  
    60. (7) Finally, the timers count up from 0x0000, so subtract 0xC350 from 0xFFFF
    61.  
    62. (8) Final Value: 0x3CAF
    63. */
    64.  
    65. WriteTimer0( 0x3CAF );        //Trigger Interrupted after 20mS
    66. WriteTimer1( 0xF63B );  //This is just a small initial delay chosen at random.
    67.  
    68.  
    69. while(1){
    70.  
    71.         servo0 = 0xEC77;// Servo 0 - Move to 0 Degrees
    72.         servo1 = 0xFB1D;// Servo 1 - Move to 90 Degrees
    73.                 Delay10KTCYx(250);        // 1 Second Delay
    74.                 Delay10KTCYx(250);
    75.         servo0 = 0xF63B;// Servo 0 - Move to 45 Degrees
    76.         servo1 = 0xF63B;// Servo 1 - Move to 45 Degrees
    77.                 Delay10KTCYx(250);        // 1 Second Delay
    78.                 Delay10KTCYx(250);
    79.         servo0 = 0xFB1D;// Servo 0 - Move to 90 Degrees
    80.         servo1 = 0xEC77;// Servo 1 - Move to 45 Degrees
    81.                 Delay10KTCYx(250);        // 1 Second Delay
    82.                 Delay10KTCYx(250);
    83.  
    84. }
    85.  
    86. }
    87.  
    88. //INTERRUPT CONTROL
    89. #pragma code InterruptVectorHigh = 0x08                //interrupt pointer address (0x18 low priority)
    90. void InterruptVectorHigh (void)
    91. {
    92.         _asm        //assembly code starts
    93.         goto InterruptHandlerHigh                //interrupt control
    94.         _endasm //assembly code ends
    95. }
    96. #pragma code
    97. #pragma interrupt InterruptHandlerHigh        //enf.
    98.  
    99. void InterruptHandlerHigh()        // declaration of InterruptHandler
    100. {//this gets ran when ever the timers flop over from FFFF->0000
    101.         if(INTCONbits.TMR0IF)                        //check if TMR0 interrupt flag is set
    102.         {
    103.                                 WriteTimer0( 0x3CAF );
    104.                                 WriteTimer1( 0xFC77 );
    105.                                 count = 0;
    106.                             INTCONbits.TMR0IF = 0;                //clear TMR0 flag              
    107.         }
    108.         if(PIR1bits.TMR1IF == 1 && PIE1bits.TMR1IE == 1)        //if set controls the first servo
    109.         {
    110.                 count++;        
    111.                                 switch(count){                
    112.                         case 1:     PORTB = 0x01; // First Stage
    113.                                             WriteTimer1( servo0 );
    114.                                                 break;
    115.                         case 2:                PORTB = 0x02; // Servo 1
    116.                                                 WriteTimer1( servo1 );
    117.                                                 break;
    118.                         case 3:                PORTB = 0x00;
    119.                                                 //PORTC = 0x02; // Swivel/Rotate
    120.                                                 WriteTimer1( servo2 );
    121.                                                 break;
    122.                         case 4:                
    123.                                                 //WriteTimer1( servo3 );
    124.                                                 break;
    125.                         case 5:                
    126.                                                 //WriteTimer1( servo4 );
    127.                                                 break;
    128.                         case 6:                
    129.                                                 //WriteTimer1( 0 );
    130.                                                 break;
    131.                                 }
    132.  
    133.                 PIR1bits.TMR1IF = 0;                        //clear Timer1 flag
    134.                 PIE1bits.TMR1IE = 1;                        //clear Timer1 enable flag set to zero
    135.         }
    136.  
    137.     INTCONbits.GIE = 1;                                //re-enable all interrupts
    138. }
     
  5. spinnaker

    AAC Fanatic!

    Oct 29, 2009
    4,887
    1,016
    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.
     
  6. MrChips

    Moderator

    Oct 2, 2009
    12,449
    3,364
    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.
     
  7. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,394
    1,606
    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.
     
    Mashadawy likes this.
  8. MMcLaren

    Well-Known Member

    Feb 14, 2010
    759
    116
    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

    Code ( (Unknown Language)):
    1. ;******************************************************************
    2. ;  K8LH Crazy-8 Hi-Rez 8-channel (PORTB) Servo Algorithm/Driver
    3. ;
    4. ;  1 usec steps, 600-2400 usecs range, using prescaler setting
    5. ;  of 1, 2, or 4 with a 4, 8, or 16-MHz clock, respectively.
    6. ;
    7. ;
    8. ;   unsigned int Servo[] = { 1500, 1500, 1500, 1500, 1500,
    9. ;                            1500, 1500, 1500, 20000 };
    10. ;
    11. ;   void interrupt()            // special event interrupts
    12. ;   { static char n = 0;        // servo array index, 0..8
    13. ;     static char Channel = 1;  // channel output bit mask
    14. ;     pir1.CCP1IF = 0;          // clear spcl event interrupt
    15. ;     portb = Channel;          // output new Servo pulse
    16. ;     ccpr1 = Servo[n++];       // update "match" period value
    17. ;     Servo[8] -= ccpr1;        // adj end-of-period off time
    18. ;     Channel <<= 1;            // prep for next channel
    19. ;     if(n == 9)                // if end of 20-msec period
    20. ;     { n = 0;                  // reset array index
    21. ;       Servo[8] = 20000;       // reset the 20 msec period
    22. ;       Channel = 1;            // reset Channel to '00000001'
    23. ;     }
    24. ;   }
    25. ;
    26.  
     
    Last edited: Jan 23, 2012
  9. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    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.
     
  10. Mashadawy

    Thread Starter New Member

    Jan 6, 2012
    4
    0
    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 ?!
     
  11. John P

    AAC Fanatic!

    Oct 14, 2008
    1,634
    224
    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.

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

    Code ( (Unknown Language)):
    1.  
    2.   bit_set(adcon, 2);                      // Fire A/D, and I think this equalizes the time
    3.   portd = 0;                                // Output low
    4.   bit_clear(t1con, 0);                             // Stop the timer
    5.   tmr1h = output_low_time >> 8;            // Make sure the compiler does this as two instructions
    6.   tmr1l = output_low_time & 0xFF;          // Again, must be two instructions
    7.   bit_set(t1con, 0);                              // Start the timer
    8.  
    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.
     
  12. ErnieM

    AAC Fanatic!

    Apr 24, 2011
    7,394
    1,606
    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.
     
Loading...