1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice

18 servo controller

Discussion in 'Microcontrollers' started by Ambient, Dec 4, 2007.

  1. Ambient

    Ambient New Member

    Joined:
    Jul 27, 2006
    Messages:
    376
    Likes:
    0
    Location:
    Massachusetts, USA
    Here is my problem:

    I need to control 18 servos for a hexapod robot. I need to be able to control all 18 servos simultaneously. I have been looking at the PWM setup, but I am not sure if this would be best, since there are not many channels on each PIC.

    Option 1:
    *use 6 PICs with at least 3 PWM outputs (one for each leg)
    *each leg PIC controlled by master PIC (brain, tells legs what coordinates to move to, also handles sensors (maybe another 10 pins))

    Option 2
    *use 18F master PIC as brain(sensor, decision handling, gives leg positions to slave PIC)
    *use one slave PIC with 18 outputs and bit-bang the pins. (receives move direction, stop, body pitch/roll/yaw, etc. modes from master)

    Option 3
    *find a PIC with 18 PWM channels

    I am hoping some of you have some experience with this, cause I cannot make up my mind as to what would be the better choice. I will be using an 18F series for the brain, since making a crude AI will be easier with C.
     
  2. HarveyH42

    HarveyH42 Banned

    Joined:
    Feb 25, 2006
    Messages:
    3,442
    Likes:
    18
    Location:
    Not Here
    I don't know how useful this will be for you, but might get some ideas from what they did. I pick it up on AVRFreaks yesterday. It controls 20 servos, the source is in 'C', and uses 2 x 4017. I think they used a ATmega16, a 40 pin chip, but only 3 pins go to the 4017s. Just took a brief look, don't know 'C'.
     

    Attached Files:

  3. Ambient

    Ambient New Member

    Joined:
    Jul 27, 2006
    Messages:
    376
    Likes:
    0
    Location:
    Massachusetts, USA
    It looks like a much simpler design then I need to use. It looks like they are cascading the servos by using the decade counter? I need to be able to control each servo in real time.

    Will a mux for the PWM signal work? Or does that signal need to be present on the servo input at all times?
     
  4. dave

    Dave New Member

    Joined:
    Jan 12, 1997
    Messages:
    -
    Likes:
    0


     
  5. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA

    You can do something similar on the PIC using the CCP module "compare" mode. The following example uses a single 74HC4017 IC and 2 PIC pins for 9 channels (on the 74HC4017 Q1..Q9 output pins). This could be expanded to 18 channels by adding another 74HC4017 driven by the CCP2 pin (and some additional interrupt code).

    Code (text):
    static unsigned char Qn = 0;
    static unsigned int  Qarray [] = { 1500, 1500, 1500, 1500,
                                       1500, 1500, 1500, 1500,
                                       1500, 20000 };  // 800..2200 range

    void isr_hi ()
    {
     /****************************************************************
      *  K8LH Crazy-8 HiRez Hard 8-channel 74HC4017 Servo Algorithm  *
      ****************************************************************/
      if (PIR1bits.CCP1IF == 1)       // if CCP1 "compare" interrupt
      {
        CCPR1H++;                     // avoid update "match"
        CCPR1 += Qarray[Qn];          // update "compare" int value
        CCPR1H--;                     // fix CCPR1H
        LATCbits.LATC1 = 0;           // clear CCP1 "CLK" line
        PIR1bits.CCP1IF = 0;          // clear CCP1 interrupt flag

        Qarray[9] -= Qarray[Qn++];    // adjust end-of-period off time

        if (Qn = 10)                  // if end-of-period
        { Qn = 0;                     // reset Qn array index
          Qarray[9] = 21000;          // reset 20.0-msec period and
          LATAbits.LATA0 = 1;         // toggle 74HC4017 "CLR" line
          LATAbits.LATA0 = 0;         // to force Q0 output sync'
        }
      }
    }
     
     
    Last edited: Dec 4, 2007
  6. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
  7. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    That's very clever and amazingly simple.

    Mike.
     
  8. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    Ouch!!! That's almost a "standard" (isochronous) soft PWM routine but you destroy the Servo array values and rely on refreshing the array from the serial port for each 20 msec cycle. And it seems the same pulse width or PWM setting applied to all of the servos results in different timing (jitter) for the different servo port groups. I would not recommend this algorithm or method for anything more than a "learning exercise"!

    Instead consider using a "standard" (isochronous) soft PWM routine which doesn't destroy the PWM array values. It'll continue to ouput pulses cycle after cycle and allows you to update one or more PWM array values at any time or interval (during your 17.5 msec 'window'). The soft PWM routine also correctly handles PWM array values of 0 (handy for LEDs) since it updates the output port from a shadow register.

    Code (text):
            clrf    PWM             ;
            setf    Shadow          ; shadow bits all on
    PWM256  movf    PWM,W           ; PWM counter 0..255
            cpfsgt  Servo+0         ; servo 0 (0..255) match?
            bcf     Shadow,0        ; yes, turn off output bit
            cpfsgt  Servo+1         ;
            bcf     Shadow,1        ;
            cpfsgt  Servo+2         ;
            bcf     Shadow,2        ;
            cpfsgt  Servo+3         ;
            bcf     Shadow,3        ;
            cpfsgt  Servo+4         ;
            bcf     Shadow,4        ;
            cpfsgt  Servo+5         ;
            bcf     Shadow,5        ;
            cpfsgt  Servo+6         ;
            bcf     Shadow,6        ;
            cpfsgt  Servo+7         ;
            bcf     Shadow,7        ;
            movff   Shadow,LATB     ; update output port
            incfsz  PWM,F           ; bump PWM counter
            bra     PWM256          ; do it again
     
     
    Last edited: Dec 5, 2007
  9. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    Mike,

    It doesn't need to make a copy as each servo value is decremented 256 times.

    I also don't understand why you think the original code would jitter? There would be a slight discrepancy between the first and last servo but it could be easily fixed (have a similar line of code to set the bits initially) and wouldn't cause jitter anyway.

    Mike.
     
  10. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    Oops, I missed that (each array element decremented 256 times). Sorry.

    But as far as jitter, he's turning on all of the port LAT bits at the same time at the beginning of the code but turning off the bits one group at a time with many cycles difference between groups. I don't see the "easy fix" you mention without using shadow registers. Please enlighten me some more? Thanks.
     
    Last edited: Dec 5, 2007
  11. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    The easy fix is to turn the bits on with similar code to in the PWM loop but with just a bsf followed by a nop for each output. This would be placed before the 500uS delay.

    Mike.
     
  12. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    I don't understand. Can you show me?

    Also, that code doesn't seem capable of handling Servo PWM values of 0 for the minimum 500 msec timing.
     
  13. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    Worst case is if the last servo pin in the decfsz array is 0 movement and nearly 1/256 resolution time has passed in the On state. Since 500us of delay at the beginning is an arbitrary value for this example, there is room for adjustment at this point to fine tune out any real physical servo movement for a 0 servo at the beginning of the routine. Maybe I should more accurately say that the resolution is 255 with a spare do nothing loop at the beginning.
     
  14. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    The code to turn on the outputs would be,
    Code (text):

        bsf  PORTA.0
        nop
        bsf  PORTA.1
        nop
        bsf  PORTA.2
        nop
        bsf  PORTA.3
        nop
        bsf  PORTA.5
        nop
        bsf  PORTB.0
        nop
     
    This would be repeated for all outputs in the same order as the pwm loop.

    Then would come the 500uS delay.

    Then would come the loop that consist of,
    Code (text):

                 decfsz    _servo+0, 1,0
                 iorlw     1
                 decfsz    _servo+1, 1,0
                 iorlw     2
                 decfsz    _servo+2, 1,0
                 iorlw     4
                 decfsz    _servo+3, 1,0
     
    The value of zero is the maximum value. The valid values being 1 to 256. You can of course shorten the 500uS delay.

    Mike.
     
  15. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    I do have other code where I incremented all the servo values to make 0 = 0:

    Code (text):
    i = 30
    FSR0ptr = @servo
    Do
       inc(POSTINC0)
       dec(i)
    Loop Until i = 0
     
  16. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    Warren;

    I apologize.

    There really is no jitter other than the normal plus or minus 1 cycle jitter associated with interrupt entry. All of the servo pulses maintain the same timing from 20 msec cycle to 20 msec cycle.

    It's just pulse width timing that's off a bit;

    Servo 00 (LATA) = 1 ---> 501.2 usecs
    Servo 05 (LATB) = 1 ---> 503.0 usecs
    Servo 13 (LATC) = 1 ---> 504.4 usecs
    Servo 19 (LATD) = 1 ---> 506.2 usecs

    No jitter...

    Mike,

    Yes, of course. Now that's a simple and elegant and (embarrassingly) intuitive solution. Of course you'd need some extra 'nops' to account for those instructions writing the the LATx registers too.

    Then modify that delay_us(500) statement to take into account the 12 cycles that occur before writing LATA and you've eliminated the pulse width timing problems...
     
    Last edited: Dec 7, 2007
  17. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    Mike,

    You don't have to apologize. Your evaluations are always worthy of note. I didn't get involved with PIC's until a couple years ago, so some of my code can certainly use some refinement. I would now write the reload of the Timer by adding the 20ms number... a trick I learned a few months ago and before I wrote the PIC RTC examples for SF.

    By the way, some of your ideas have turned out to be an inspiration for some new stuff I'm working on.
     
  18. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    Ok guys, I'm impressed.

    Between the two of you you've shown me that a jitter-free lo-rez 'soft' solution with more than 8 channels (which has eluded me) is feasable and probably practical (just not in the current form of Warren's demo')

    Now, could you build interface code that would allow single channel serial updates instead of that 30 byte serial update? Are there any pins left over to allow using one pin for hardware handshaking to the host to signal when we're sitting in that 17.5 msec 'window' (where we can use the serial port) instead of sending that flag character?

    Warren,

    I learn a lot by disecting code and your code is no exception. In this case, decrementing the pwm array values 256 times is better because it would work with 16F' devices too.
     
  19. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    You need to provide each Servo with a pulse of 1000 to 2000 usecs duration every 20 msecs.

    You can use the CCP module PWM mode but you need to break up the 20 msec servo period into smaller faster PWM frames in order to maintain usable hi-rez pulse width resolution.

    You can multiplex the PWM output signal by using it to drive one of the enable lines on a 74HC238 IC. Change the address lines on the 74HC4017 to direct a 1000 to 2000 usec PWM pulse to one of the eight 74HC238 outputs every 2500 usec interval.

    While this provides a wonderful hi-rez no-jitter solution, it will only provide you with 16 servo outputs using two 74HC238 ICs and 5 pins (CCP1, CCP2, and 3 pins for decoder address lines). The following example is for 4 MHz (prescaler 1:1) or 16 MHz (prescaler 4:1) clocks.

    Code (text):
    /*                                                               *
    *  array1 and array2 elements hold Servo pulse width values     *
    *  ranging from 3000..9000 in 250-nsec 'ticks' representing an  *
    *  extended pulse width range of 750-usecs to 2250-usecs        *
    *                                                               */
    static unsigned int array1 [] = { 6000, 6000, 6000, 6000,
                                      6000, 6000, 6000, 6000 };
    static unsigned int array2 [] = { 6000, 6000, 6000, 6000,
                                      6000, 6000, 6000, 6000 };

    /*****************************************************************
    *  K8LH 16-Channel 74HC238 Hi-Rez 'PWM' Servo Algorithm Driver  *
    *****************************************************************/
    #pragma interrupt isr_hi

    void isr_hi ()
    { if(PIR1bits.TMR2IF)
      { PIR1bits.TMR2IF = 0;            // clear TMR2 interrupt flag
       /**************************************************************
        *                                                            *
        *  setup 74HC238 address lines and our work variables for    *
        *  the next servo channel interval during the final (10th)   *
        *  frame of the current servo channel interval (the final    *
        *  frame in each interval will always have a 0% duty cycle   *
        *  and all 74HC238 outputs are 'off' so it will be safe to   *
        *  change the address lines)                                 *
        *                                                            */
        if(frame == 10)             // 10 PWM frames/Servo interval
        { frame = 0;                // reset PWM frame counter
          select %= 8;              // 8 Servo intervals/20-msec cycle
          pulse1 = array1[select];
          pulse2 = array2[select];
          PORTB = (PORTB & 0b11111000) | select;
          select++;                 // bump for next interval
        }
        frame++;                    // bump for next interrupt
       /**************************************************************
        *  setup CCP1 PWM duty cycle for next 250-usec PWM 'frame'   *
        *                                                            */
        if(pulse1 > 1000)           // if pulse1 > 250-usecs
        { CCPR1L = 250;             // do a 100% duty cycle frame
          pulse1 -= 1000;           // subtract 250-usecs
        }
        else                        // do a variable or 0% frame
        { CCP1CONbits.CCP1Y = (pulse1 & 1);
          CCP1CONbits.CCP1X = (pulse1 & 2);
          CCPR1L = (pulse1 >>= 2);
          pulse1 = 0;               // remaining frames are %0
        }

       /**************************************************************
        *  setup CCP2 PWM duty cycle for next 250-usec PWM 'frame'   *
        *                                                            */
        if(pulse2 > 1000)           // if pulse2 > 250-usecs
        { CCPR2L = 250;             // do a 100% duty cycle frame
          pulse2 -= 1000;           // subtract 250-usecs
        }
        else                        // do a variable or 0% frame
        { CCP2CONbits.CCP2Y = (pulse1 & 1);
          CCP2CONbits.CCP2X = (pulse1 & 2);
          CCPR2L = (pulse2 >>= 2);
          pulse2 = 0;               // remaining frames are %0
        }
      }
    }
     
    If 16 channels isn't enough and a software or hardware 18 channel solution seems like too much baggage, you might consider using a couple 9 channel "slaves" connected via RS232. The drawing below shows 8 channels but it could easily be changed to include the 74HC4017 Q9 output for 9 channels with an 800..2200 usec range for each servo.

    [​IMG]
     
    Last edited: Jan 3, 2016
  20. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    An interesting proposition for the communication. The TX pin could be used as a hardware CTS I suppose and then turn on USART/UART for RX only on the RX pin. I don't know if this is what you're inferring. Or, are you thinking of a more compact method of transmitting the data?

    I'm referring to your previous posting above.... not the last post.
     
    Last edited: Dec 5, 2007
  21. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    You've got it. Could hardware handshaking eliminate the requirement for the Host to watch for that flag character. Just to simplify host requirements...
     

Share This Page