18 servo controller

Status
Not open for further replies.
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.
 
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:

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:
Pommie said:
There is no need to worry about the serial communication. At 19200 baud a byte can arrive each 5mS,

Make that about every 500uS... 10 Bytes can arrive in a bit over 5mS @ 19200 baud.
 
Whoops, you are correct, I must remember to keep track of that decimal point in future.

Mike.
 
You could use the indirect register as a fifo.

Replace
Code:
	iorlw	4 
	andwf	LATE, 1,0 
	nop 
	nop 
	nop 
	nop 
	nop 
	incfsz	_pos, 1,0 
	bra	$-76
With
Code:
	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:
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:
Mike said:
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,

Here is my attempt at caching the serial data.

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

Code:
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:
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:
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:
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:
'  Swordfish BASIC
'
  DIM RxBuffer(256) As Byte

  FSR0 = @RxBuffer
 
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.
 

You have the same problem I tried to find a work around. I can't see how to solve this without disableing interrupts.

Mike.
 
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.
 
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....
 
Mike said:
Warren,

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

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.
 
wschroeder said:
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....

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.
 
Pommie said:
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.

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:
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.
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…