IMPOSSIBLE? 30 Servos with USART Comm on Single 40-pin PIC

General discussion on mikroBasic.
Post Reply
Author
Message
xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

IMPOSSIBLE? 30 Servos with USART Comm on Single 40-pin PIC

#1 Post by xor » 20 Sep 2006 00:24

Yes....it works. PWM was tested with 18F452 @ 40MHz and completes all 30 servo positions individually and with 256 resolution in 2.5ms. This code can also be adapted to operate 30 servos on a 16F877A @ 20MHz with all 30 servo positions completed in 5ms.

All that control for only 530 ROM! :D

Code: Select all

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]

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 outputs with 256 positions each
    delay_us(500)
          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
             nop
             nop
             nop
             nop
             incfsz    _pos, 1,0
             bra       $-76
          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

main:
  _init
  USART_Init(19200)                            ' 19200 should be minimum speed...faster is better
                                               ' communicate with PC every 20ms
     While true
        If aa = 1 Then                         ' interrupt occurred?...time to check for new servo values
           aa = 0                              ' prepare for next interrupt
           USART_Write(128)                    ' send some value to get PC's attention
           For tmp = 0 to 29                   ' get 30 servo values from PC
              While USART_DATA_READY = 0       ' wait until Usart Read buffer has a byte
                If inc(tmr) = 0 Then Break End If
              Wend
              servo[tmp] = USART_Read + 1      ' store servo value
           Next tmp                            ' servo data transfer should complete in ~15ms @ 19200 baud
        End If
     Wend

end.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

iceblu3710
Posts: 13
Joined: 14 Nov 2007 23:48

#2 Post by iceblu3710 » 23 Mar 2008 04:03

Awesome project, makes it look so easy!

Im starting to build my own hexapod and was thinking of using a servo control chip and a pic for collecting sensor data and feeding them into an arm7 main processor for the brains of the operation.

I need to control 26 servos and was starting to get pretty worried but this is great. I have a few questions though, I dont have an 18F452 yet and my scope is across country as im still moving so could you give me a rundown on a few things?

The pulse timing is pretty standard across servos but what controls the time high and the time low? (I assume TMR0H/T0CON do)

The USART communication, the pic sends a euro symbol to the PC and then its looks like the next 30 numbers correlate to servo[0-29] but what is the error rate in reading, Ive had alot of problems with my 16f877a double reading on Usart_Read() or even missing it completely?

Last question as sence this is all on a pic and timer driven is it suseptable to skew? I only program in c/c++ and would like to add some functionality to the usart communications as well an eeprom for servo stat positions. Would I be better off dedicating this pic to solely this code and use another one with the main program and input management?

xor
Posts: 5465
Joined: 18 May 2005 00:59
Location: NYC
Contact:

#3 Post by xor » 23 Mar 2008 04:27

Through collaboration and tweaks the design is commercial with USB comm:
http://www.circuit-ed.com/30-Servo-Hard ... 17C10.aspx

Here is the next generation for USB and USART:

http://www.jvmbots.com/proyectos/jvmservo/Hard_28_I.pdf
http://www.jvmbots.com/proyectos/jvmservo/Lib28_v1.zip


I now have a single 40-pin PIC 33 servo driver design with full USART capability.
[color=darkred][b]xor[/b][/color]
[url=http://circuit-ed.com]CircuitED -[/url]

iceblu3710
Posts: 13
Joined: 14 Nov 2007 23:48

#4 Post by iceblu3710 » 23 Mar 2008 09:04

Hey thanks for the quick response, I have no idea about ASM but I just went through the code step by step with the datasheet and a notepad and learned how it works. I converted it over to C and documented how the ASM works so newer people can understand how it was achieved, I hope you don't mind.

I still have two questions though:

1)

Code: Select all

incfsz _pos, 1,0
pos=0 on call of interrupt() and that asm command says +1 then evaluate for zero. How does this loop reach zero to skip the branch command?

2) How did you choose the values of TMR0H and TMR0L? From the datasheet I understand their padding for the TMR0IF so would I be correct in assuming this:
0xFFFF(65536) - 0x153E(5438) = 0xEAC1(60097)
60097 = 17.5ms then the entire TMR0 would take 19.08ms to reach 0xFFFF yes?

3) T0CON = 0b00000100; the TMR0 prescaler assignment bit isnt set shouldn't it be T0CON = 0b00001100; TMR0's clock must come from the prescaler as 40MHz/4/32=312.5KHz or every 3.12us

Thanks for the help!

Code: Select all

/**********************************************************************
'******                                                          ******
'******    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
'********************************************************************/

unsigned short int aa, pos, tmp;
unsigned long int tmr;
unsigned short int servo[30];

void interrupt() {
    LATA = 0b00111111;                                  // turn on all servos on PortA
    LATB = 0b11111111;                                  // turn on all servos on PortB
    LATC = 0b00111111;                                  // turn on all servos on PORTC
    LATD = 0b11111111;                                  // turn on all servos on PortD
    LATE = 0b00000111;                                  // turn on all servos on PORTE
    pos = 0;                                            // manage outputs with 256 positions each
    delay_us(500);
          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                   // w = 0b00000000
             decfsz    _servo+5, 1,0       // servo[255]-1, do next command
             iorlw     1                   // w = 0b00000000 OR 0b00000001, w = 0b00000001
             decfsz    _servo+6, 1,0       // servo[255]-1, do next command
             iorlw     2                   // w = 0b00000001 OR 0b00000010, w = 0b00000011
             decfsz    _servo+7, 1,0       // servo[0]-1, SKIP next command!
             iorlw     4                   // w = 0b00000011 OR 0b00000100, w = 0b00000111
             decfsz    _servo+8, 1,0       // servo[255]-1, do next command
             iorlw     8                   // w = 0b00000011 OR 0b00001000, w = 0b00001011
             decfsz    _servo+9, 1,0       // servo[255]-1, do next command
             iorlw     16                  // w = 0b00001011 OR 0b00010000, w = 0b00011011
             decfsz    _servo+10, 1,0      // servo[255]-1, do next command
             iorlw     32                  // w = 0b00011011 OR 0b00100000, w = 0b00111011
             decfsz    _servo+11, 1,0      // servo[255]-1, do next command
             iorlw     64                  // w = 0b00111011 OR 0b01000000, w = 0b01111011
             decfsz    _servo+12, 1,0      // servo[255]-1, do next command
             iorlw     128                 // w = 0b01111011 OR 0b10000000, w = 0b11111011
             andwf     LATB, 1,0           // LATB = 0b11111111 AND w = 0b11111011, LATB = 0b11111011
             movlw     0
             decfsz    _servo+13, 1,0      // ** Explination ** //
             iorlw     1                   // Each pass w holds the mask of whats > 0 (0b1) and
             decfsz    _servo+14, 1,0      // whats at zero (0b0). This AND'd againsts LATB makes
             iorlw     2                   // a comparason so that the servo whos value is 0
             decfsz    _servo+15, 1,0      // will goto logical zero (off)
             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
             nop
             nop
             nop
             nop
             incfsz    _pos, 1,0                // _pos+1, save to _pos, skip next statement if zero
             bra       $-76                     // branch back 76 commands (1st movlw 0)
          }                                             // 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;
}

void init_pic() {
   LATA = 0;                                             // Pull ports low A:E
   LATB = 0;
   LATC = 0;
   LATD = 0;
   LATE = 0;
   TRISA = 0;                                            // Setup ports for output A:E
   TRISB = 0;
   TRISC = 0;
   TRISD = 0;
   TRISE = 0;
   ADCON1 = 0b00000111;
   T0CON = 0b00000100;                                   // prescaler = 32 or 3.2us per increment
   TMR0H = -2;                                           // load high byte first
   TMR0L = 0;                                            // load low byte second
   INTCON = 0b10100000;                                  // Timer0 Interrupt Enabled and Flag Cleared
   T0CON.7 = 1;                                          // Timer0.Bit(7) On
   aa = 0;                                               // Init inturupt toggle
}

void main() {
  init_pic();
  USART_Init(19200);                                     // 19200 should be minimum speed...faster is better
                                                         // communicate with PC every 20ms
     While(1) {
        If(aa = 1) {                                     // interrupt occurred?...time to check for new servo values
           aa = 0;                                       // prepare for next interrupt
           USART_Write(128);                             // send euro symbol to get PC's attention
           For(tmp = 0, temp < 29, temp++) {             // get 30 servo values from PC
              While(USART_DATA_READY = 0) {              // wait until Usart Read buffer has a byte
                If(inc(tmr) = 0) Break;
              }
              servo[tmp] = USART_Read() + 1;             // store servo value
           }                                             // servo data transfer should complete in ~15ms @ 19200 baud
        }
     }
}
[/code]

*** NOTE: It compiles without any errors but I have no way of testing it, thx.

edwinvarghesea1
Posts: 50
Joined: 25 Feb 2014 00:19

Re: IMPOSSIBLE? 30 Servos with USART Comm on Single 40-pin P

#5 Post by edwinvarghesea1 » 25 Feb 2014 00:22

Can anybody help me to convert this code to c language for pic 16f877A??? please..... thanks in advance

naje
Posts: 10
Joined: 17 Feb 2015 12:51

Re: IMPOSSIBLE? 30 Servos with USART Comm on Single 40-pin P

#6 Post by naje » 01 Apr 2015 17:03

can you convert this code to mikroc for pic please

bluec
Posts: 1
Joined: 07 Jul 2016 00:24

Re: IMPOSSIBLE? 30 Servos with USART Comm on Single 40-pin P

#7 Post by bluec » 07 Jul 2016 00:38

First of all, sorry for my bad english. I know i'm here a little bit late but may i ask xor some question? I have read this topic http://forum.mikroe.com/viewtopic.php?f=10&t=13607 of you and write the same code but in C language. It works perfectly, but now i want to control about 18-24 servos for my next hexapod project. The question is, can i use the same method with 2 CCP modules to control 18-24 servos? Hope to see your reply!

Post Reply

Return to “mikroBasic General”