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. blueroomelectronics

    blueroomelectronics Well-Known Member

    Joined:
    Jan 21, 2007
    Messages:
    12,536
    Likes:
    170
    Location:
    Toronto, Canada
    Brilliant piece of code that 30 servo, I'll put it in one of the Junebug / Firefly tutorials.
    The more I work with 18F PICs (in assembly) the less I want to use the 14bit core PICs.
     
  2. Ambient

    Ambient New Member

    Joined:
    Jul 27, 2006
    Messages:
    376
    Likes:
    0
    Location:
    Massachusetts, USA
    So it might just work then, for 10 channel max with one pin, using soft PWM. From what I have seen, the PW is 10% max required for full travel. So practically, I can get 9 channels max on one pin using a mux. Does that sound like it will work?

    Also, it looks like I can do it all with one small PIC, with an 18F and C code running the sensors and "brain". Or would it still be best to use the PWM pins and use one PIC per leg (some 16F's have pins to use in PWM mode)?

    Thanks for the help guys.
     
    Last edited: Dec 5, 2007
  3. blueroomelectronics

    blueroomelectronics Well-Known Member

    Joined:
    Jan 21, 2007
    Messages:
    12,536
    Likes:
    170
    Location:
    Toronto, Canada
    It would appear to be possible with almost any PIC.
     
  4. dave

    Dave New Member

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


     
  5. Pommie

    Pommie Well-Known Member Most Helpful Member

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

    Mike,

    There is no need to worry about the serial communication. At 19200 baud a byte can arrive each 5mS, this fact combined with the 2 byte hardware buffer means that overflow cannot occur and, consequently, no handshake is required. You could simply send something like 128+servo number, high nibble/16, low nibble.

    Edit, I'm out by a factor of 10 as pointed out by Warren below.

    Mike.
     
    Last edited: Dec 5, 2007
  6. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    Make that about every 500uS... 10 Bytes can arrive in a bit over 5mS @ 19200 baud.
     
  7. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    Whoops, you are correct, I must remember to keep track of that decimal point in future.:eek:

    Mike.
     
  8. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    You could use the indirect register as a fifo.

    Replace
    Code (text):

        iorlw   4
        andwf   LATE, 1,0
        nop
        nop
        nop
        nop
        nop
        incfsz  _pos, 1,0
        bra $-76
     
    With
    Code (text):

        iorlw   4        
        andwf   LATE, 1,0  
        btfss   PIR1,RCIF   ;byte recieved
        goto    DoneRS      ;no, so go do a delay
        movfw   RXREG       ;yes, get it and
        movwf   POSTINC0    ;store it in buffer
        bcf PIR1,RCIF   ;clr IF
    CarryOn incfsz  _pos, 1,0      
        bra $-76       


    DoneRS  goto    CarryOn     ;just delay and jump back
     
    At the end of the interrupt, the fifo could be emptied into a more usable array and the FSR register restored.

    I'm not sure how usable the FSR register is with Basic but it should be fine as long as it is saved and restored in the asm part.

    Mike.
     
    Last edited: Dec 5, 2007
  9. Mike - K8LH

    Mike - K8LH Well-Known Member

    Joined:
    Jan 22, 2005
    Messages:
    3,642
    Likes:
    109
    Location:
    Michigan, USA
    Tying up the processor for 2500 usecs without being able to cache serial data in a circular buffer as it's coming in is a potentially big problem, in my opinion.

    Mike,

    Just spotted your idea. That would actually work quite well and Main could easily determine the number of characters in the buffer by the value of FSR0L. BTW, you don't need the bcf PIR1,RCIF instruction as it's cleared automatically when you pull a character from RCREG...
     
    Last edited: Dec 5, 2007
  10. Pommie

    Pommie Well-Known Member Most Helpful Member

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

    Here is my attempt at caching the serial data.

    I have guessed the basic syntax and how variables are accessed.

    Code (text):

    program SERVO30_18F452
    '**********************************************************************
    '******                                                          ******
    '******    30 Servo Driver @ 256 Positions using P18 @ 40MHz     ******
    '******            by W. Schroeder on July 22, 2006              ******
    '******   REVISED on Sept 19, 2006..added USART & code tweaks    ******
    '******   Compiled with mikroBASIC 5.0.0.1 & Tested on 18F452    ******
    '******                                                          ******
    '******      Servo routines use only ~2.5ms of 20ms cycle        ******
    '******   This ensures enough time for changing servo values     ******
    '******                                                          ******
    '******   USART is used for communicating new servo values to    ******
    '******   the PIC.  It is important that the PIC initiate this   ******
    '******   communication with the PC.  Therefore, PC software     ******
    '******   must regularly scan the Comm Port for receipt of a     ******
    '******   an initiate-communication signal.  The PC will send    ******
    '******   exactly 30 bytes which represent the servo values.     ******
    '******                                                          ******
    '**********************************************************************
    '       These are the port pin assignments for the servo array:
    '            servo[0]- PORTA.0
    '            servo[1]- PORTA.1
    '            servo[2]- PORTA.2
    '            servo[3]- PORTA.3
    '            servo[4]- PORTA.5  <-- note: skips RA4 open-drain output
    '            servo[5]- PORTB.0
    '            servo[6]- PORTB.1
    '            servo[7]- PORTB.2
    '            servo[8]- PORTB.3
    '            servo[9]- PORTB.4
    '            servo[10]-PORTB.5
    '            servo[11]-PORTB.6
    '            servo[12]-PORTB.7
    '            servo[13]-PORTC.0
    '            servo[14]-PORTC.1
    '            servo[15]-PORTC.2
    '            servo[16]-PORTC.3
    '            servo[17]-PORTC.4
    '            servo[18]-PORTC.5
    '            servo[19]-PORTD.0
    '            servo[20]-PORTD.1
    '            servo[21]-PORTD.2
    '            servo[22]-PORTD.3
    '            servo[23]-PORTD.4
    '            servo[24]-PORTD.5
    '            servo[25]-PORTD.6
    '            servo[26]-PORTD.7
    '            servo[27]-PORTE.0
    '            servo[28]-PORTE.1
    '            servo[29]-PORTE.2
    '********************************************************************

    dim aa, pos, tmp as byte
    dim tmr as word
    dim servo as byte[30]
    dim saveFSR as byte
    dim buffer as byte[10]
    dim bufferlen as byte
    dim bufferpoint as byte
    dim position as byte

    sub procedure interrupt
        LATA = 63                                  ' turn on all servos on PortA
        LATB = 255                                 ' turn on all servos on PortB
        LATC = 63                                  ' turn on all servos on PORTC
        LATD = 255                                 ' turn on all servos on PortD
        LATE = 7                                   ' turn on all servos on PORTE
        pos = 0                                    ' manage 8 outputs with 256 positions each
        delay_us(500)
              ASM                                  ' VERY efficient PWM masking routine
                movfw   FSR0
                movwf   saveFSR
                movlw   _buffer            'maybe this should be &buffer
                movwf   FSR0                   'so FSR points to buffer
                                                   'not sure how banking works here
                movlw     0
                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
                iorlw     8
                decfsz    _servo+4, 1,0
                iorlw     32
                andwf     LATA, 1,0
                movlw     0
                decfsz    _servo+5, 1,0
                iorlw     1
                decfsz    _servo+6, 1,0
                iorlw     2
                decfsz    _servo+7, 1,0
                iorlw     4
                decfsz    _servo+8, 1,0
                iorlw     8
                decfsz    _servo+9, 1,0
                iorlw     16
                decfsz    _servo+10, 1,0
                iorlw     32
                decfsz    _servo+11, 1,0
                iorlw     64
                decfsz    _servo+12, 1,0
                iorlw     128
                andwf     LATB, 1,0
                movlw     0
                decfsz    _servo+13, 1,0
                iorlw     1
                decfsz    _servo+14, 1,0
                iorlw     2
                decfsz    _servo+15, 1,0
                iorlw     4
                decfsz    _servo+16, 1,0
                iorlw     8
                decfsz    _servo+17, 1,0
                iorlw     16
                decfsz    _servo+18, 1,0
                iorlw     32
                andwf     LATC, 1,0
                movlw     0
                decfsz    _servo+19, 1,0
                iorlw     1
                decfsz    _servo+20, 1,0
                iorlw     2
                decfsz    _servo+21, 1,0
                iorlw     4
                decfsz    _servo+22, 1,0
                iorlw     8
                decfsz    _servo+23, 1,0
                iorlw     16
                decfsz    _servo+24, 1,0
                iorlw     32
                decfsz    _servo+25, 1,0
                iorlw     64
                decfsz    _servo+26, 1,0
                iorlw     128
                andwf     LATD, 1,0
                movlw     0
                decfsz    _servo+27, 1,0
                iorlw     1
                decfsz    _servo+28, 1,0
                iorlw     2
                decfsz    _servo+29, 1,0
                iorlw     4
                andwf     LATE, 1,0
                btfsz     PIR1,RCIF
                goto      DoneRS
                movfw     RXREG
                movwf     POSTINC0
                bcf       PIR1,RCIF
    CarryOn     incfsz    _pos, 1,0
                bra       $-76

                movlw     _buffer
                subfw     FSR0,W        ;W=FSR0-&buffer = len received
                movwf     _bufferlen
                movfw     saveFSR       ;restore FSR
                movwf     FSR0


              END ASM                              ' these routines have used ~2500us to this point
        TMR0H= Hi(-5438)                           ' balance of time for 20ms cycle (=17.5ms)
        TMR0L = -5438
        INTCON.TMR0IF = 0                          ' clear interrupt flag
        aa = 1
    end sub

    sub procedure _init
       LATA = 0
       LATB = 0
       LATC = 0
       LATD = 0
       LATE = 0
       TRISA = 0
       TRISB = 0
       TRISC = 0
       TRISD = 0
       TRISE = 0
       ADCON1 = 7
       T0CON = %00000100                           ' prescaler = 32 or 3.2us per increment
       TMR0H = -2                                  ' load high byte first
       TMR0L = 0                                   ' load low byte second
       INTCON = %10100000                          ' Timer0 Interrupt Enabled and Flag Cleared
       T0CON.7 = 1                                 ' Timer0 On
       aa = 0
    end sub

    sub function getRS232 as byte
    'this relies on the buffer being flushed before another interrupt comes along
    dim temp as int
        temp=-1
        While temp=-1
            if bufferlen<>0 then                   'any bytes in buffer
                temp=buffer[bufferpoint]
                bufferpoint=bufferpoint+1
                if bufferpoint=bufferlen then      'read all bytes in buffer?
                    bufferpoint=0
                    bufferlen=0
                endif
            else
                aa=0                               'clear the interrupt flag
                ASM
                btfss PIR1,RCIF                    'byte available
                goto  NoRS232                      'no carry on
                bcf   INTCON,7                     'dissable interrupts - can't see a way around this
                movfw aa                           'has an interrupt occured and stolen our data
                btfss STATUS,Z                     'if so,
                goto  NoRS232                      'go around the outer loop and get it from the buffer
                movfw RXREG                        'get byte
                bsf   INTCON,7                     'reenable interrupts
                movwf _temp                        'byte to return
                clrf  _temp+1                      'ensure temp <>-1
    NoRS232     bsf   INTCON,7                     'needed for when goto (above) executed
                END ASM
            endif
        Wend
    end sub

    main:
      _init
      USART_Init(19200)                            ' 19200 should be minimum speed...faster is better
                                                   ' communicate with PC every 20ms
        While true
        do
                tmp=getRS232
        loop until tmp>127                     'wait for servo number
        tmp=tmp & 127
        position = getRS232*16                 'get servo position
        position = position + getRS232
            servo[tmp] = position                  ' store servo value
        Wend

    end.
    Mike.
    Edit, Mike, I couldn't find a way around disabling interrupts. Have you any ideas?

    Edit2, changed code.
     
    Last edited: Dec 5, 2007
  11. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    This is untested right now, but it should allow simple USART RX source at any baudrate (maybe 115K should be the upper limit). The only stipulation is that the PC starts communication after the PIC has been started.

    It's written presently in mikroBasic and will compile with the free version:

    Code (text):
    program SERVO30_USART

    '**********************************************************************
    '******                                                          ******
    '******    30 Servo Driver @ 256 Positions using P18 @ 40MHz     ******
    '******            by W. Schroeder on July 22, 2006              ******
    '******                                                          ******
    '******   REVISED on Sept 19, 2006..added USART & code tweaks    ******
    '******   Compiled with mikroBASIC 5.0.0.1 & Tested on 18F452    ******
    '******                                                          ******
    '******   REVISED on Dec 5, 2007.. added USART for any Baud      ******
    '******   Compiled with mikroBASIC 6.0 & Tested on 18F452        ******
    '******                                                          ******
    '******      Servo routines use only ~2.5ms of 20ms cycle        ******
    '******      USART at any baudrate to change servo values        ******
    '******                                                          ******
    '**********************************************************************
    '       These are the port pin assignments for the servo array:
    '            servo[0]- PORTA.0
    '            servo[1]- PORTA.1
    '            servo[2]- PORTA.2
    '            servo[3]- PORTA.3
    '            servo[4]- PORTA.5
    '            servo[5]- PORTB.0
    '            servo[6]- PORTB.1
    '            servo[7]- PORTB.2
    '            servo[8]- PORTB.3
    '            servo[9]- PORTB.4
    '            servo[10]-PORTB.5
    '            servo[11]-PORTB.6
    '            servo[12]-PORTB.7
    '            servo[13]-PORTC.0
    '            servo[14]-PORTC.1
    '            servo[15]-PORTC.2
    '            servo[16]-PORTC.3
    '            servo[17]-PORTC.4
    '            servo[18]-PORTC.5
    '            servo[19]-PORTD.0
    '            servo[20]-PORTD.1
    '            servo[21]-PORTD.2
    '            servo[22]-PORTD.3
    '            servo[23]-PORTD.4
    '            servo[24]-PORTD.5
    '            servo[25]-PORTD.6
    '            servo[26]-PORTD.7
    '            servo[27]-PORTE.0
    '            servo[28]-PORTE.1
    '            servo[29]-PORTE.2
    '********************************************************************

    const T1_20ms as word = 65536-50000+6      ' 20ms + stop timer1 stop compensation
    dim pos as byte
    dim servo as byte[30]
    dim servobuffer as byte[30]
    dim lastservobufaddr as word
    dim T1 As word absolute $FCE

    sub procedure interrupt
        LATA = 63                              ' turn on all servos on PortA
        LATB = 255                             ' turn on all servos on PortB
        LATC = 63                              ' turn on all servos on PORTC
        LATD = 255                             ' turn on all servos on PortD
        LATE = 7                               ' turn on all servos on PORTE
        pos = 0                                ' 256 counts.. rollover to original value
        delay_us(500)                          ' 0 position delay; adjust to suit needs
              ASM                              ' VERY efficient PWM masking routine
                 movlw     0
                 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
                 iorlw     8
                 decfsz    _servo+4, 1,0
                 iorlw     32
                 andwf     LATA, 1,0
                 movlw     0
                 decfsz    _servo+5, 1,0
                 iorlw     1
                 decfsz    _servo+6, 1,0
                 iorlw     2
                 decfsz    _servo+7, 1,0
                 iorlw     4
                 decfsz    _servo+8, 1,0
                 iorlw     8
                 decfsz    _servo+9, 1,0
                 iorlw     16
                 decfsz    _servo+10, 1,0
                 iorlw     32
                 decfsz    _servo+11, 1,0
                 iorlw     64
                 decfsz    _servo+12, 1,0
                 iorlw     128
                 andwf     LATB, 1,0
                 movlw     0
                 decfsz    _servo+13, 1,0
                 iorlw     1
                 decfsz    _servo+14, 1,0
                 iorlw     2
                 decfsz    _servo+15, 1,0
                 iorlw     4
                 decfsz    _servo+16, 1,0
                 iorlw     8
                 decfsz    _servo+17, 1,0
                 iorlw     16
                 decfsz    _servo+18, 1,0
                 iorlw     32
                 andwf     LATC, 1,0
                 movlw     0
                 decfsz    _servo+19, 1,0
                 iorlw     1
                 decfsz    _servo+20, 1,0
                 iorlw     2
                 decfsz    _servo+21, 1,0
                 iorlw     4
                 decfsz    _servo+22, 1,0
                 iorlw     8
                 decfsz    _servo+23, 1,0
                 iorlw     16
                 decfsz    _servo+24, 1,0
                 iorlw     32
                 decfsz    _servo+25, 1,0
                 iorlw     64
                 decfsz    _servo+26, 1,0
                 iorlw     128
                 andwf     LATD, 1,0
                 movlw     0
                 decfsz    _servo+27, 1,0
                 iorlw     1
                 decfsz    _servo+28, 1,0
                 iorlw     2
                 decfsz    _servo+29, 1,0
                 iorlw     4
                 andwf     LATE, 1,0
                 nop
                 btfsc     PIR1, 5,0           ' check USART RX buffer
                 movff     RCREG, POSTINC2     ' move RX byte into servobuffer
                 nop
                 incfsz    _pos, 1,0
                 bra       $-76                ' loop total of 20224 cycles = 2.02ms
              END ASM
        T1CON.TMR1ON = 0                       ' stop timer1
        T1 = T1 + T1_20ms                      ' reload timer1
        T1CON.TMR1ON = 1                       ' restart timer1
        PIR1.TMR1IF = 0                        ' clear interrupt flag
    end sub

    sub procedure _init
       LATA = 0
       LATB = 0
       LATC = 0
       LATD = 0
       LATE = 0
       TRISA = 0
       TRISB = 0
       TRISC = 0
       TRISD = 0
       TRISE = 0
       ADCON1 = 7                              ' disable ADC's
       lastservobufaddr = @servobuffer + 30
       INTCON = 192                            ' enable GIE & PEIE
       T1CON = 32                              ' prescaler=4, timer off
       T1 = 0                                  ' load Timer1
       PIE1.TMR1IE = 1                         ' enable Timer1 Interrupt
       PIR1.TMR1IF = 0                         ' clear Timer1 Interrupt Flag
       T1CON.TMR1ON = 1                        ' start Timer1
    end sub

    main:
      _init
      USART_Init(115200)                       ' any baudrate is permissible
      FSR0ptr = @servo
      While FSR0ptr < (@servo + 30)            ' initialize all servo values to 0 position
         POSTINC0 = 1
      Wend
      FSR2ptr = @servobuffer
      While true

         While FSR2ptr < lastservobufaddr      ' FSR2 is RX servobuffer
            Do
            Loop Until PIR1.RCIF = 1           ' wait for new RX byte
            POSTINC2 = RCREG                   ' load RX byte.. when 30 bytes move on
         Wend
         FSR2ptr = @servobuffer                ' reset RX buffer
         FSR1ptr = @servobuffer                ' setup tranfer of RX array to
         FSR0ptr = @servo                      ' servo work array
         While FSR1ptr < lastservobufaddr
            POSTINC0 = inc(POSTINC1)           ' transfer RX buffer to work values.. add 1
         Wend
         
      Wend
    end.
     
    Last edited: Dec 5, 2007
  12. Mike - K8LH

    Mike - K8LH Well-Known Member

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

    Your idea actually borders on "genius". I'm not familiar with MikroBASIC but in Swordfish BASIC it's very easy to setup one of the FSR registers to point directly at an array so you could deposit incoming characters directly into the array. Pretty cool, huh?

    Code (text):
    '  Swordfish BASIC
    '
      DIM RxBuffer(256) As Byte

      FSR0 = @RxBuffer
     
     
  13. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    I prefer SF over mB for a lot of reasons. I started PIC's with mB and ASM, so I still run my code off some of my original stuff in mB. I am having some problems with inline ASM in SF and that's why I chose mB, because I can debug it quicker in case of problems.
     
  14. Mike - K8LH

    Mike - K8LH Well-Known Member

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

    Perfect 5 cycle timing, true or false. Well done.
     
  15. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    You have the same problem I tried to find a work around. I can't see how to solve this without disableing interrupts.

    Mike.
     
  16. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    Forget the last post. The problem only occurs when you don't have file to file transfer and auto resetting of RCIF. Actually, can't a related problem occur. The interrupt reading the byte and clearing the flag, making RXREG invalid.

    Mike.
     
  17. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    I was troubled by the same thoughts when I realized that the interrupt could occur at any point. The saving part is that the RCIF flag will not clear until the RX buffer is read and empty. Just in case, the ISR will finish the job.... :)
     
  18. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    Fortunately, hobby servos don't need the type of precisions we apply to the code. When I realize what some champion RC flyer does with a handful of parts and a 555 timer, I know I'm living in another world.

    Oh.. and the stop timer thing is a Bob Ammerman trick I think.
     
  19. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    However, if the interrupt occus where I indicated then a byte may get inserted twice, once by the ISR and later by the following statement. The only way to stop this (I can think of) is do disable interrupts.

    Mike.
     
  20. wschroeder

    wschroeder New Member

    Joined:
    Nov 11, 2007
    Messages:
    65
    Likes:
    0
    Location:
    NYC
    It can't. The instruction to pass the USART RXBuffer to the RX Array is a single instruction "MOVFF" and there is also a POSTINC are part of that instruction. The stored Program Counter is either at or after this instrucion when jumping to the ISR. At least I hope so...

    Edit:
    I took a look again.... you have a point I think... interesting.
     
    Last edited: Dec 5, 2007
  21. Pommie

    Pommie Well-Known Member Most Helpful Member

    Joined:
    Mar 18, 2005
    Messages:
    10,161
    Likes:
    340
    Location:
    Brisbane Australia
    BTW, when you originally posted the link I assumed someone else had written the code. As it's now clear that you wrote it, can I congratulate you on a very clever and elegant solution.

    Mike.
     

Share This Page